/* This file is part of TALER Copyright (C) 2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file backenddb/pg_insert_transfer_details.c * @brief Implementation of the insert_transfer_details function for Postgres * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "pg_insert_transfer_details.h" #include "pg_helper.h" /** * How often do we re-try if we run into a DB serialization error? */ #define MAX_RETRIES 3 enum GNUNET_DB_QueryStatus TMH_PG_insert_transfer_details ( void *cls, const char *instance_id, const char *exchange_url, const char *payto_uri, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_EXCHANGE_TransferData *td) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; uint64_t credit_serial; unsigned int retries; retries = 0; check_connection (pg); PREPARE (pg, "lookup_credit_serial", "SELECT" " credit_serial" " FROM merchant_transfers" " WHERE exchange_url=$1" " AND wtid=$4" " AND account_serial=" " (SELECT account_serial" " FROM merchant_accounts" " WHERE payto_uri=$2" " AND exchange_url=$1" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$3))"); PREPARE (pg, "insert_transfer_signature", "INSERT INTO merchant_transfer_signatures" "(credit_serial" ",signkey_serial" ",credit_amount" ",wire_fee" ",execution_time" ",exchange_sig) " "SELECT $1, signkey_serial, $2, $3, $4, $5" " FROM merchant_exchange_signing_keys" " WHERE exchange_pub=$6" " ORDER BY start_date DESC" " LIMIT 1"); PREPARE (pg, "insert_transfer_to_coin_mapping", "INSERT INTO merchant_transfer_to_coin" "(deposit_serial" ",credit_serial" ",offset_in_exchange_list" ",exchange_deposit_value" ",exchange_deposit_fee) " "SELECT deposit_serial, $1, $2, $3, $4" " FROM merchant_deposits" " JOIN merchant_contract_terms USING (order_serial)" " WHERE coin_pub=$5" " AND h_contract_terms=$6" " AND merchant_serial=" " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id=$7)"); PREPARE (pg, "update_wired_by_coin_pub", "WITH os AS" /* select orders affected by the coin */ "(SELECT order_serial" " FROM merchant_deposits" " WHERE coin_pub=$1)" "UPDATE merchant_contract_terms " " SET wired=TRUE " " WHERE order_serial IN " " (SELECT order_serial FROM merchant_deposits" /* only orders for which NO un-wired coin exists*/ " WHERE NOT EXISTS " " (SELECT order_serial FROM merchant_deposits" /* orders for which ANY un-wired coin exists */ " JOIN os USING (order_serial)" /* filter early */ " WHERE deposit_serial NOT IN" " (SELECT deposit_serial " /* all coins associated with order that WERE wired */ " FROM merchant_deposits " " JOIN os USING (order_serial)" /* filter early */ " JOIN merchant_deposit_to_transfer USING (deposit_serial)" " JOIN merchant_transfers USING (credit_serial)" " WHERE confirmed=TRUE)))"); RETRY: if (MAX_RETRIES < ++retries) return GNUNET_DB_STATUS_SOFT_ERROR; if (GNUNET_OK != TMH_PG_start_read_committed (pg, "insert transfer details")) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } /* lookup credit serial */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (exchange_url), GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("credit_serial", &credit_serial), GNUNET_PQ_result_spec_end }; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "lookup_credit_serial", params, rs); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); TMH_PG_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'lookup_credit_serial' for account %s and amount %s failed with status %d\n", payto_uri, TALER_amount2s (&td->total_amount), qs); return qs; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { TMH_PG_rollback (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'lookup_credit_serial' for account %s failed with transfer unknown\n", payto_uri); return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } } /* update merchant_transfer_signatures table */ { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&credit_serial), TALER_PQ_query_param_amount_with_currency (pg->conn, &td->total_amount), TALER_PQ_query_param_amount_with_currency (pg->conn, &td->wire_fee), GNUNET_PQ_query_param_timestamp (&td->execution_time), GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig), GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_transfer_signature", params); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); TMH_PG_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'insert_transfer_signature' failed with status %d\n", qs); return qs; } if (0 == qs) { TMH_PG_rollback (pg); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'insert_transfer_signature' failed with status %d\n", qs); return GNUNET_DB_STATUS_HARD_ERROR; } } /* Update transfer-coin association table */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating transfer-coin association table\n"); for (unsigned int i = 0; idetails_length; i++) { const struct TALER_TrackTransferDetails *d = &td->details[i]; uint64_t i64 = (uint64_t) i; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&credit_serial), GNUNET_PQ_query_param_uint64 (&i64), TALER_PQ_query_param_amount_with_currency (pg->conn, &d->coin_value), TALER_PQ_query_param_amount_with_currency (pg->conn, &d->coin_fee), /* deposit fee */ GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms), GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_transfer_to_coin_mapping", params); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); TMH_PG_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'insert_transfer_to_coin_mapping' failed with status %d\n", qs); return qs; } if (0 == qs) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "'insert_transfer_to_coin_mapping' failed at %u: deposit unknown\n", i); } } /* Update merchant_contract_terms 'wired' status: for all coins that were wired, set the respective order's "wired" status to true, *if* all other deposited coins associated with that order have also been wired (this time or earlier) */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating contract terms 'wired' status\n"); for (unsigned int i = 0; idetails_length; i++) { const struct TALER_TrackTransferDetails *d = &td->details[i]; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (&d->coin_pub), GNUNET_PQ_query_param_end }; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_wired_by_coin_pub", params); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); TMH_PG_rollback (pg); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "'update_wired_by_coin_pub' failed with status %d\n", qs); return qs; } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Committing transaction...\n"); qs = TMH_PG_commit (pg); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) goto RETRY; return qs; }