/*
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;
}