/* This file is part of TALER (C) 2014--2021 Taler Systems SA 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 ANASTASISABILITY 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 sync/plugin_syncdb_postgres.c * @brief database helper functions for postgres used by sync * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include "sync_database_plugin.h" #include "sync_database_lib.h" /** * Type of the "cls" argument given to each of the functions in * our API. */ struct PostgresClosure { /** * Postgres connection handle. */ struct GNUNET_PQ_Context *conn; /** * Directory with SQL statements to run to create tables. */ char *sql_dir; /** * Underlying configuration. */ const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Name of the currently active transaction, NULL if none is active. */ const char *transaction_name; /** * Currency we accept payments in. */ char *currency; /** * Did we initialize the prepared statements * for this session? */ bool init; }; /** * Drop sync tables * * @param cls closure our `struct Plugin` * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static enum GNUNET_GenericReturnValue postgres_drop_tables (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_Context *conn; conn = GNUNET_PQ_connect_with_cfg (pg->cfg, "syncdb-postgres", "drop", NULL, NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); return GNUNET_OK; } /** * Establish connection to the database. * * @param cls plugin context * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static enum GNUNET_GenericReturnValue prepare_statements (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_PreparedStatement ps[] = { GNUNET_PQ_make_prepare ("account_insert", "INSERT INTO accounts " "(account_pub" ",expiration_date" ") VALUES " "($1,$2);", 2), GNUNET_PQ_make_prepare ("payment_insert", "INSERT INTO payments " "(account_pub" ",order_id" ",token" ",timestamp" ",amount_val" ",amount_frac" ") VALUES " "($1,$2,$3,$4,$5,$6);", 6), GNUNET_PQ_make_prepare ("payment_done", "UPDATE payments " "SET" " paid=TRUE " "WHERE" " order_id=$1" " AND" " account_pub=$2" " AND" " paid=FALSE;", 2), GNUNET_PQ_make_prepare ("account_update", "UPDATE accounts " "SET" " expiration_date=$1 " "WHERE" " account_pub=$2;", 2), GNUNET_PQ_make_prepare ("account_select", "SELECT" " expiration_date " "FROM" " accounts " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("payments_select", "SELECT" " account_pub" ",order_id" ",amount_val" ",amount_frac" " FROM payments" " WHERE paid=FALSE;", 0), GNUNET_PQ_make_prepare ("payments_select_by_account", "SELECT" " timestamp" ",order_id" ",token" ",amount_val" ",amount_frac" " FROM payments" " WHERE" " paid=FALSE" " AND" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("gc_accounts", "DELETE FROM accounts " "WHERE" " expiration_date < $1;", 1), GNUNET_PQ_make_prepare ("gc_pending_payments", "DELETE FROM payments " "WHERE" " paid=FALSE" " AND" " timestamp < $1;", 1), GNUNET_PQ_make_prepare ("backup_insert", "INSERT INTO backups " "(account_pub" ",account_sig" ",prev_hash" ",backup_hash" ",data" ") VALUES " "($1,$2,$3,$4,$5);", 5), GNUNET_PQ_make_prepare ("backup_update", "UPDATE backups " " SET" " backup_hash=$1" ",account_sig=$2" ",prev_hash=$3" ",data=$4" " WHERE" " account_pub=$5" " AND" " backup_hash=$6;", 6), GNUNET_PQ_make_prepare ("backup_select_hash", "SELECT " " backup_hash " "FROM" " backups " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("backup_select", "SELECT " " account_sig" ",prev_hash" ",backup_hash" ",data " "FROM" " backups " "WHERE" " account_pub=$1;", 1), GNUNET_PQ_make_prepare ("do_commit", "COMMIT", 0), GNUNET_PQ_PREPARED_STATEMENT_END }; enum GNUNET_GenericReturnValue ret; ret = GNUNET_PQ_prepare_statements (pg->conn, ps); if (GNUNET_OK != ret) return ret; pg->init = true; return GNUNET_OK; } /** * Connect to the database if the connection does not exist yet. * * @param pg the plugin-specific state * @param skip_prepare true if we should skip prepared statement setup * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue internal_setup (struct PostgresClosure *pg, bool skip_prepare) { if (NULL == pg->conn) { #if AUTO_EXPLAIN /* Enable verbose logging to see where queries do not properly use indices */ struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), /* https://wiki.postgresql.org/wiki/Serializable suggests to really force the default to 'serializable' if SSI is to be used. */ GNUNET_PQ_make_try_execute ( "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), GNUNET_PQ_EXECUTE_STATEMENT_END }; #else struct GNUNET_PQ_ExecuteStatement *es = NULL; #endif struct GNUNET_PQ_Context *db_conn; db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg, "syncdb-postgres", NULL, es, NULL); if (NULL == db_conn) return GNUNET_SYSERR; pg->conn = db_conn; } if (NULL == pg->transaction_name) GNUNET_PQ_reconnect_if_down (pg->conn); if (pg->init) return GNUNET_OK; if (skip_prepare) return GNUNET_OK; return prepare_statements (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 * @return #GNUNET_OK if everything is fine * #GNUNET_NO if a transaction was rolled back * #GNUNET_SYSERR on hard errors */ static enum GNUNET_GenericReturnValue postgres_preflight (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("ROLLBACK"), GNUNET_PQ_EXECUTE_STATEMENT_END }; if (! pg->init) { if (GNUNET_OK != internal_setup (pg, false)) return GNUNET_SYSERR; } if (NULL == pg->transaction_name) return GNUNET_OK; /* all good */ if (GNUNET_OK == GNUNET_PQ_exec_statements (pg->conn, es)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check rolled back transaction `%s'!\n", pg->transaction_name); } else { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BUG: Preflight check failed to rollback transaction `%s'!\n", pg->transaction_name); } pg->transaction_name = NULL; return GNUNET_NO; } /** * Check that the database connection is still up. * * @param cls a `struct PostgresClosure` with connection to check */ static void check_connection (void *cls) { struct PostgresClosure *pg = cls; GNUNET_PQ_reconnect_if_down (pg->conn); } /** * 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 enum GNUNET_GenericReturnValue begin_transaction (void *cls, const char *name) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), GNUNET_PQ_EXECUTE_STATEMENT_END }; check_connection (pg); postgres_preflight (pg); pg->transaction_name = name; if (GNUNET_OK != GNUNET_PQ_exec_statements (pg->conn, es)) { TALER_LOG_ERROR ("Failed to start transaction\n"); GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Roll back the current transaction of a database connection. * * @param cls the `struct PostgresClosure` with the plugin-specific state */ static void rollback (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { GNUNET_PQ_make_execute ("ROLLBACK"), GNUNET_PQ_EXECUTE_STATEMENT_END }; if (GNUNET_OK != GNUNET_PQ_exec_statements (pg->conn, es)) { TALER_LOG_ERROR ("Failed to rollback transaction\n"); GNUNET_break (0); } pg->transaction_name = NULL; } /** * Commit the current transaction of a database connection. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @return transaction status code */ static enum GNUNET_DB_QueryStatus commit_transaction (void *cls) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam no_params[] = { GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "do_commit", no_params); pg->transaction_name = NULL; return qs; } /** * Function called to perform "garbage collection" on the * database, expiring records we no longer require. Deletes * all user records that are not paid up (and by cascade deletes * the associated recovery documents). Also deletes expired * truth and financial records older than @a fin_expire. * * @param cls closure * @param expire_backups backups older than the given time stamp should be garbage collected * @param expire_pending_payments payments still pending from since before * this value should be garbage collected * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_gc (void *cls, struct GNUNET_TIME_Absolute expire_backups, struct GNUNET_TIME_Absolute expire_pending_payments) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_absolute_time (&expire_backups), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_QueryParam params2[] = { TALER_PQ_query_param_absolute_time (&expire_pending_payments), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_accounts", params); if (qs < 0) return qs; return GNUNET_PQ_eval_prepared_non_select (pg->conn, "gc_pending_payments", params2); } /** * Store payment. Used to begin a payment, not indicative * that the payment actually was made. (That is done * when we increment the account's lifetime.) * * @param cls closure * @param account_pub account to store @a backup under * @param order_id order we create * @param token claim token to use, NULL for none * @param amount how much we asked for * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_store_payment (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const char *order_id, const struct TALER_ClaimTokenP *token, const struct TALER_Amount *amount) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct TALER_ClaimTokenP tok; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (&tok), GNUNET_PQ_query_param_absolute_time (&now), TALER_PQ_query_param_amount (amount), GNUNET_PQ_query_param_end }; if (NULL == token) memset (&tok, 0, sizeof (tok)); else tok = *token; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "payment_insert", params); switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); return SYNC_DB_HARD_ERROR; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Closure for #payment_by_account_cb. */ struct PaymentIteratorContext { /** * Function to call on each result */ SYNC_DB_PaymentPendingIterator it; /** * Closure for @e it. */ void *it_cls; /** * Plugin context. */ struct PostgresClosure *pg; /** * Query status to return. */ enum GNUNET_DB_QueryStatus qs; }; /** * Helper function for #postgres_lookup_pending_payments_by_account(). * To be called with the results of a SELECT statement * that has returned @a num_results results. * * @param cls closure of type `struct PaymentIteratorContext *` * @param result the postgres result * @param num_result the number of results in @a result */ static void payment_by_account_cb (void *cls, PGresult *result, unsigned int num_results) { struct PaymentIteratorContext *pic = cls; for (unsigned int i = 0; i < num_results; i++) { struct GNUNET_TIME_Absolute timestamp; char *order_id; struct TALER_Amount amount; struct TALER_ClaimTokenP token; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_absolute_time ("timestamp", ×tamp), GNUNET_PQ_result_spec_string ("order_id", &order_id), GNUNET_PQ_result_spec_auto_from_type ("token", &token), TALER_PQ_result_spec_amount ("amount", pic->pg->currency, &amount), GNUNET_PQ_result_spec_end }; if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, i)) { GNUNET_break (0); pic->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } pic->qs = i + 1; pic->it (pic->it_cls, timestamp, order_id, &token, &amount); GNUNET_PQ_cleanup_result (rs); } } /** * Lookup pending payments by account. * * @param cls closure * @param account_pub account to look for pending payments under * @param it iterator to call on all pending payments * @param it_cls closure for @a it * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_lookup_pending_payments_by_account (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, SYNC_DB_PaymentPendingIterator it, void *it_cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct PaymentIteratorContext pic = { .it = it, .it_cls = it_cls, .pg = pg }; enum GNUNET_DB_QueryStatus qs; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, "payments_select_by_account", params, &payment_by_account_cb, &pic); if (qs > 0) return pic.qs; GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); return qs; } /** * Store backup. Only applicable for the FIRST backup under * an @a account_pub. Use @e update_backup_TR to update an * existing backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig signature affirming storage request * @param backup_hash hash of @a backup * @param backup_size number of bytes in @a backup * @param backup raw data to backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_store_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const struct SYNC_AccountSignatureP *account_sig, const struct GNUNET_HashCode *backup_hash, size_t backup_size, const void *backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_HashCode bh; static struct GNUNET_HashCode no_previous_hash; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_auto_from_type (account_sig), GNUNET_PQ_query_param_auto_from_type (&no_previous_hash), GNUNET_PQ_query_param_auto_from_type (backup_hash), GNUNET_PQ_query_param_fixed_size (backup, backup_size), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "backup_insert", params); } switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* First, check if account exists */ { struct GNUNET_TIME_Absolute ed; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &ed), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* account exists, check if existing backup conflicts */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", &bh), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* original error must have been a hard error, oddly enough */ return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* had an existing backup, is it identical? */ if (0 != GNUNET_memcmp (&bh, backup_hash)) /* previous conflicting backup exists */ return SYNC_DB_OLD_BACKUP_MISMATCH; /* backup identical to what was provided, no change */ return SYNC_DB_NO_RESULTS; } /** * Update backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig signature affirming storage request * @param old_backup_hash hash of the previous backup (must match) * @param backup_hash hash of @a backup * @param backup_size number of bytes in @a backup * @param backup raw data to backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_update_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const struct GNUNET_HashCode *old_backup_hash, const struct SYNC_AccountSignatureP *account_sig, const struct GNUNET_HashCode *backup_hash, size_t backup_size, const void *backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_HashCode bh; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (backup_hash), GNUNET_PQ_query_param_auto_from_type (account_sig), GNUNET_PQ_query_param_auto_from_type (old_backup_hash), GNUNET_PQ_query_param_fixed_size (backup, backup_size), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_auto_from_type (old_backup_hash), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "backup_update", params); } switch (qs) { case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* handle interesting case below */ break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); return SYNC_DB_HARD_ERROR; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* First, check if account exists */ { struct GNUNET_TIME_Absolute ed; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &ed), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* account exists, check if existing backup conflicts */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", &bh), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_OLD_BACKUP_MISSING; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* handle interesting case below */ break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* had an existing backup, is it identical? */ if (0 == GNUNET_memcmp (&bh, backup_hash)) { /* backup identical to what was provided, no change */ return SYNC_DB_NO_RESULTS; } if (0 == GNUNET_memcmp (&bh, old_backup_hash)) /* all constraints seem satisfied, original error must have been a hard error */ return SYNC_DB_HARD_ERROR; /* previous backup does not match old_backup_hash */ return SYNC_DB_OLD_BACKUP_MISMATCH; } /** * Lookup an account and associated backup meta data. * * @param cls closure * @param account_pub account to store @a backup under * @param backup_hash[OUT] set to hash of @a backup * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_lookup_account (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, struct GNUNET_HashCode *backup_hash) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; check_connection (pg); postgres_preflight (pg); { struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("backup_hash", backup_hash), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select_hash", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: break; /* handle interesting case below */ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } /* check if account exists */ { struct GNUNET_TIME_Absolute expiration; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("expiration_date", &expiration), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* indicates: no account */ return SYNC_DB_PAYMENT_REQUIRED; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* indicates: no backup */ return SYNC_DB_NO_RESULTS; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Obtain backup. * * @param cls closure * @param account_pub account to store @a backup under * @param account_sig[OUT] set to signature affirming storage request * @param prev_hash[OUT] set to hash of previous @a backup, all zeros if none * @param backup_hash[OUT] set to hash of @a backup * @param backup_size[OUT] set to number of bytes in @a backup * @param backup[OUT] set to raw data to backup, caller MUST FREE */ static enum SYNC_DB_QueryStatus postgres_lookup_backup (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, struct SYNC_AccountSignatureP *account_sig, struct GNUNET_HashCode *prev_hash, struct GNUNET_HashCode *backup_hash, size_t *backup_size, void **backup) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("account_sig", account_sig), GNUNET_PQ_result_spec_auto_from_type ("prev_hash", prev_hash), GNUNET_PQ_result_spec_auto_from_type ("backup_hash", backup_hash), GNUNET_PQ_result_spec_variable_size ("data", backup, backup_size), GNUNET_PQ_result_spec_end }; check_connection (pg); postgres_preflight (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "backup_select", params, rs); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Increment account lifetime. * * @param cls closure * @param account_pub which account received a payment * @param order_id order which was paid, must be unique and match pending payment * @param lifetime for how long is the account now paid (increment) * @return transaction status */ static enum SYNC_DB_QueryStatus postgres_increment_lifetime (void *cls, const struct SYNC_AccountPublicKeyP *account_pub, const char *order_id, struct GNUNET_TIME_Relative lifetime) { struct PostgresClosure *pg = cls; struct GNUNET_TIME_Absolute expiration; enum GNUNET_DB_QueryStatus qs; check_connection (pg); if (GNUNET_OK != begin_transaction (pg, "increment lifetime")) { GNUNET_break (0); return SYNC_DB_HARD_ERROR; } { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (order_id), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "payment_done", params); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); rollback (pg); return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); rollback (pg); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: rollback (pg); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } } { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_absolute_time ("expiration_date", &expiration), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "account_select", params, rs); } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: rollback (pg); return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: rollback (pg); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_end }; expiration = GNUNET_TIME_relative_to_absolute (lifetime); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "account_insert", params); } break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_auto_from_type (account_pub), GNUNET_PQ_query_param_end }; expiration = GNUNET_TIME_absolute_add (expiration, lifetime); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "account_update", params); } break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: rollback (pg); return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: rollback (pg); GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); rollback (pg); return SYNC_DB_NO_RESULTS; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } qs = commit_transaction (pg); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: return SYNC_DB_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return SYNC_DB_SOFT_ERROR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return SYNC_DB_ONE_RESULT; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return SYNC_DB_ONE_RESULT; default: GNUNET_break (0); return SYNC_DB_HARD_ERROR; } } /** * Initialize tables. * * @param cls the `struct PostgresClosure` with the plugin-specific state * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static enum GNUNET_GenericReturnValue postgres_create_tables (void *cls) { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; conn = GNUNET_PQ_connect_with_cfg (pc->cfg, "syncdb-postgres", "sync-", NULL, NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); return GNUNET_OK; } /** * Initialize Postgres database subsystem. * * @param cls a configuration instance * @return NULL on error, otherwise a `struct TALER_SYNCDB_Plugin` */ void * libsync_plugin_db_postgres_init (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct SYNC_DatabasePlugin *plugin; pg = GNUNET_new (struct PostgresClosure); pg->cfg = cfg; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "syncdb-postgres", "SQL_DIR", &pg->sql_dir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "syncdb-postgres", "SQL_DIR"); GNUNET_free (pg); return NULL; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", "CURRENCY", &pg->currency)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } if (GNUNET_OK != internal_setup (pg, true)) { GNUNET_free (pg->currency); GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } plugin = GNUNET_new (struct SYNC_DatabasePlugin); plugin->cls = pg; plugin->create_tables = &postgres_create_tables; plugin->drop_tables = &postgres_drop_tables; plugin->preflight = &postgres_preflight; plugin->gc = &postgres_gc; plugin->store_payment_TR = &postgres_store_payment; plugin->lookup_pending_payments_by_account_TR = &postgres_lookup_pending_payments_by_account; plugin->store_backup_TR = &postgres_store_backup; plugin->lookup_account_TR = &postgres_lookup_account; plugin->lookup_backup_TR = &postgres_lookup_backup; plugin->update_backup_TR = &postgres_update_backup; plugin->increment_lifetime_TR = &postgres_increment_lifetime; return plugin; } /** * Shutdown Postgres database subsystem. * * @param cls a `struct SYNC_DB_Plugin` * @return NULL (always) */ void * libsync_plugin_db_postgres_done (void *cls) { struct SYNC_DatabasePlugin *plugin = cls; struct PostgresClosure *pg = plugin->cls; GNUNET_PQ_disconnect (pg->conn); GNUNET_free (pg->sql_dir); GNUNET_free (pg); GNUNET_free (plugin); return NULL; } /* end of plugin_syncdb_postgres.c */