bank API refactoring for #7276 (incomplete)

This commit is contained in:
Christian Grothoff 2022-11-17 13:28:15 +01:00
parent 8e0f06c86b
commit 741831e87b
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
8 changed files with 541 additions and 488 deletions

View File

@ -77,6 +77,11 @@ static enum GNUNET_GenericReturnValue
parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
const json_t *history)
{
struct TALER_BANK_CreditHistoryResponse chr = {
.http_status = MHD_HTTP_OK,
.ec = TALER_EC_NONE,
.response = history
};
json_t *history_array;
if (NULL == (history_array = json_object_get (history,
@ -90,49 +95,45 @@ parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; i<json_array_size (history_array); i++)
{
struct TALER_BANK_CreditDetails td;
uint64_t row_id;
struct GNUNET_JSON_Specification hist_spec[] = {
TALER_JSON_spec_amount_any ("amount",
&td.amount),
GNUNET_JSON_spec_timestamp ("date",
&td.execution_date),
GNUNET_JSON_spec_uint64 ("row_id",
&row_id),
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&td.reserve_pub),
GNUNET_JSON_spec_string ("debit_account",
&td.debit_account_uri),
GNUNET_JSON_spec_string ("credit_account",
&td.credit_account_uri),
GNUNET_JSON_spec_end ()
};
json_t *transaction = json_array_get (history_array,
i);
size_t len = json_array_size (history_array);
struct TALER_BANK_CreditDetails cd[len];
if (GNUNET_OK !=
GNUNET_JSON_parse (transaction,
hist_spec,
NULL, NULL))
for (unsigned int i = 0; i<json_array_size (history_array); i++)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
struct TALER_BANK_CreditDetails *td = &cd[i];
struct GNUNET_JSON_Specification hist_spec[] = {
TALER_JSON_spec_amount_any ("amount",
&td->amount),
GNUNET_JSON_spec_timestamp ("date",
&td->execution_date),
GNUNET_JSON_spec_uint64 ("row_id",
&td->serial_id),
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&td->reserve_pub),
GNUNET_JSON_spec_string ("debit_account",
&td->debit_account_uri),
GNUNET_JSON_spec_string ("credit_account",
&td->credit_account_uri),
GNUNET_JSON_spec_end ()
};
json_t *transaction = json_array_get (history_array,
i);
if (GNUNET_OK !=
GNUNET_JSON_parse (transaction,
hist_spec,
NULL,
NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
if (GNUNET_OK !=
hh->hcb (hh->hcb_cls,
MHD_HTTP_OK,
TALER_EC_NONE,
row_id,
&td,
transaction))
{
hh->hcb = NULL;
GNUNET_JSON_parse_free (hist_spec);
return GNUNET_OK;
}
GNUNET_JSON_parse_free (hist_spec);
chr.details.success.details_length = len;
chr.details.success.details = cd;
hh->hcb (hh->hcb_cls,
&chr);
}
return GNUNET_OK;
}
@ -152,72 +153,67 @@ handle_credit_history_finished (void *cls,
const void *response)
{
struct TALER_BANK_CreditHistoryHandle *hh = cls;
const json_t *j = response;
enum TALER_ErrorCode ec;
struct TALER_BANK_CreditHistoryResponse chr = {
.http_status = MHD_HTTP_OK,
.response = response
};
hh->job = NULL;
switch (response_code)
{
case 0:
ec = TALER_EC_GENERIC_INVALID_RESPONSE;
chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
parse_account_history (hh,
j))
chr.response))
{
GNUNET_break_op (0);
json_dumpf (j,
json_dumpf (chr.response,
stderr,
JSON_INDENT (2));
response_code = 0;
ec = TALER_EC_GENERIC_INVALID_RESPONSE;
chr.http_status = 0;
chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
ec = TALER_EC_NONE;
break;
TALER_BANK_credit_history_cancel (hh);
return;
case MHD_HTTP_NO_CONTENT:
ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
ec = TALER_JSON_get_error_code (j);
chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says the HTTP Authentication
failed. May happen if HTTP authentication is used and the
user supplied a wrong username/password combination. */
ec = TALER_JSON_get_error_code (j);
chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify: the bank is either unaware
of the endpoint (not a bank), or of the account.
We should pass the JSON (?) reply to the application */
ec = TALER_JSON_get_error_code (j);
chr.ec = TALER_JSON_get_error_code (chr.response);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
ec = TALER_JSON_get_error_code (j);
chr.ec = TALER_JSON_get_error_code (chr.response);
break;
default:
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u\n",
(unsigned int) response_code);
ec = TALER_JSON_get_error_code (j);
chr.ec = TALER_JSON_get_error_code (chr.response);
break;
}
if (NULL != hh->hcb)
hh->hcb (hh->hcb_cls,
response_code,
ec,
0LLU,
NULL,
j);
hh->hcb (hh->hcb_cls,
&chr);
TALER_BANK_credit_history_cancel (hh);
}

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2017--2021 Taler Systems SA
Copyright (C) 2017--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
@ -77,6 +77,11 @@ static enum GNUNET_GenericReturnValue
parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh,
const json_t *history)
{
struct TALER_BANK_DebitHistoryResponse dhr = {
.http_status = MHD_HTTP_OK,
.ec = TALER_EC_NONE,
.response = history
};
json_t *history_array;
if (NULL == (history_array = json_object_get (history,
@ -90,51 +95,47 @@ parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; i<json_array_size (history_array); i++)
{
struct TALER_BANK_DebitDetails td;
uint64_t row_id;
struct GNUNET_JSON_Specification hist_spec[] = {
TALER_JSON_spec_amount_any ("amount",
&td.amount),
GNUNET_JSON_spec_timestamp ("date",
&td.execution_date),
GNUNET_JSON_spec_uint64 ("row_id",
&row_id),
GNUNET_JSON_spec_fixed_auto ("wtid",
&td.wtid),
GNUNET_JSON_spec_string ("credit_account",
&td.credit_account_uri),
GNUNET_JSON_spec_string ("debit_account",
&td.debit_account_uri),
GNUNET_JSON_spec_string ("exchange_base_url",
&td.exchange_base_url),
GNUNET_JSON_spec_end ()
};
json_t *transaction = json_array_get (history_array,
i);
size_t len = json_array_size (history_array);
struct TALER_BANK_DebitDetails dd[len];
if (GNUNET_OK !=
GNUNET_JSON_parse (transaction,
hist_spec,
NULL, NULL))
for (unsigned int i = 0; i<len; i++)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
struct TALER_BANK_DebitDetails *td = &dd[i];
struct GNUNET_JSON_Specification hist_spec[] = {
TALER_JSON_spec_amount_any ("amount",
&td->amount),
GNUNET_JSON_spec_timestamp ("date",
&td->execution_date),
GNUNET_JSON_spec_uint64 ("row_id",
&td->serial_id),
GNUNET_JSON_spec_fixed_auto ("wtid",
&td->wtid),
GNUNET_JSON_spec_string ("credit_account",
&td->credit_account_uri),
GNUNET_JSON_spec_string ("debit_account",
&td->debit_account_uri),
GNUNET_JSON_spec_string ("exchange_base_url",
&td->exchange_base_url),
GNUNET_JSON_spec_end ()
};
json_t *transaction = json_array_get (history_array,
i);
if (GNUNET_OK !=
GNUNET_JSON_parse (transaction,
hist_spec,
NULL,
NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
if (GNUNET_OK !=
hh->hcb (hh->hcb_cls,
MHD_HTTP_OK,
TALER_EC_NONE,
row_id,
&td,
transaction))
{
hh->hcb = NULL;
GNUNET_JSON_parse_free (hist_spec);
return GNUNET_OK;
}
GNUNET_JSON_parse_free (hist_spec);
dhr.details.success.details_length = len;
dhr.details.success.details = dd;
hh->hcb (hh->hcb_cls,
&dhr);
}
return GNUNET_OK;
}
@ -154,69 +155,64 @@ handle_debit_history_finished (void *cls,
const void *response)
{
struct TALER_BANK_DebitHistoryHandle *hh = cls;
const json_t *j = response;
enum TALER_ErrorCode ec;
struct TALER_BANK_DebitHistoryResponse dhr = {
.http_status = MHD_HTTP_OK,
.response = response
};
hh->job = NULL;
switch (response_code)
{
case 0:
ec = TALER_EC_GENERIC_INVALID_RESPONSE;
dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
parse_account_history (hh,
j))
dhr.response))
{
GNUNET_break_op (0);
response_code = 0;
ec = TALER_EC_GENERIC_INVALID_RESPONSE;
dhr.http_status = 0;
dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
ec = TALER_EC_NONE;
break;
TALER_BANK_debit_history_cancel (hh);
return;
case MHD_HTTP_NO_CONTENT:
ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the bank is buggy
(or API version conflict); just pass JSON reply to the application */
GNUNET_break_op (0);
ec = TALER_JSON_get_error_code (j);
dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_UNAUTHORIZED:
/* Nothing really to verify, bank says the HTTP Authentication
failed. May happen if HTTP authentication is used and the
user supplied a wrong username/password combination. */
ec = TALER_JSON_get_error_code (j);
dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify: the bank is either unaware
of the endpoint (not a bank), or of the account.
We should pass the JSON (?) reply to the application */
ec = TALER_JSON_get_error_code (j);
dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
ec = TALER_JSON_get_error_code (j);
dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
default:
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u\n",
(unsigned int) response_code);
ec = TALER_JSON_get_error_code (j);
dhr.ec = TALER_JSON_get_error_code (dhr.response);
break;
}
if (NULL != hh->hcb)
hh->hcb (hh->hcb_cls,
response_code,
ec,
0LLU,
NULL,
j);
hh->hcb (hh->hcb_cls,
&dhr);
TALER_BANK_debit_history_cancel (hh);
}

View File

@ -152,83 +152,72 @@ do_shutdown (void *cls)
/**
* Callback used to process ONE entry in the transaction
* Callback used to process the transaction
* history returned by the bank.
*
* @param cls closure
* @param http_status HTTP status code from server
* @param ec taler error code
* @param serial_id identification of the position at
* which we are returning data
* @param details details about the wire transfer
* @param json original full response from server
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to
* abort iteration
* @param reply response we got from the bank
*/
static enum GNUNET_GenericReturnValue
static void
credit_history_cb (void *cls,
unsigned int http_status,
enum TALER_ErrorCode ec,
uint64_t serial_id,
const struct TALER_BANK_CreditDetails *details,
const json_t *json)
const struct TALER_BANK_CreditHistoryResponse *reply)
{
(void) cls;
chh = NULL;
if (MHD_HTTP_OK != http_status)
switch (reply->http_status)
{
if ( (MHD_HTTP_NO_CONTENT != http_status) ||
(TALER_EC_NONE != ec) )
{
if (0 == http_status)
{
fprintf (stderr,
"Failed to obtain HTTP reply from `%s'\n",
auth.wire_gateway_url);
}
else
{
fprintf (stderr,
"Failed to obtain credit history from `%s': HTTP status %u (%s)\n",
auth.wire_gateway_url,
http_status,
TALER_ErrorCode_get_hint (ec));
}
if (NULL != json)
json_dumpf (json,
stderr,
JSON_INDENT (2));
global_ret = 2;
GNUNET_SCHEDULER_shutdown ();
return GNUNET_NO;
}
case 0:
fprintf (stderr,
"Failed to obtain HTTP reply from `%s'\n",
auth.wire_gateway_url);
global_ret = 2;
break;
case MHD_HTTP_NO_CONTENT:
fprintf (stdout,
"End of transactions list.\n");
"No transactions.\n");
global_ret = 0;
GNUNET_SCHEDULER_shutdown ();
return GNUNET_NO;
break;
case MHD_HTTP_OK:
for (unsigned int i = 0; i<reply->details.success.details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd =
&reply->details.success.details[i];
/* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) &&
(0 != strcasecmp (credit_account,
cd->credit_account_uri) ) )
continue;
if ( (NULL != debit_account) &&
(0 != strcasecmp (debit_account,
cd->debit_account_uri) ) )
continue;
fprintf (stdout,
"%llu: %s->%s (%s) over %s at %s\n",
(unsigned long long) cd->serial_id,
cd->debit_account_uri,
cd->credit_account_uri,
TALER_B2S (&cd->reserve_pub),
TALER_amount2s (&cd->amount),
GNUNET_TIME_timestamp2s (cd->execution_date));
}
global_ret = 0;
break;
default:
fprintf (stderr,
"Failed to obtain credit history from `%s': HTTP status %u (%s)\n",
auth.wire_gateway_url,
reply->http_status,
TALER_ErrorCode_get_hint (reply->ec));
if (NULL != reply->response)
json_dumpf (reply->response,
stderr,
JSON_INDENT (2));
global_ret = 2;
break;
}
/* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) &&
(0 != strcasecmp (credit_account,
details->credit_account_uri) ) )
return GNUNET_OK;
if ( (NULL != debit_account) &&
(0 != strcasecmp (debit_account,
details->debit_account_uri) ) )
return GNUNET_OK;
fprintf (stdout,
"%llu: %s->%s (%s) over %s at %s\n",
(unsigned long long) serial_id,
details->debit_account_uri,
details->credit_account_uri,
TALER_B2S (&details->reserve_pub),
TALER_amount2s (&details->amount),
GNUNET_TIME_timestamp2s (details->execution_date));
return GNUNET_OK;
GNUNET_SCHEDULER_shutdown ();
}
@ -264,84 +253,71 @@ execute_credit_history (void)
/**
* Function with the debit debit transaction history.
* Function with the debit transaction history.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
* 0 if the bank's reply is bogus (fails to follow the protocol),
* #MHD_HTTP_NO_CONTENT if there are no more results; on success the
* last callback is always of this status (even if `abs(num_results)` were
* already returned).
* @param ec detailed error code
* @param serial_id monotonically increasing counter corresponding to the transaction
* @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
* @param reply response details
*/
static enum GNUNET_GenericReturnValue
static void
debit_history_cb (void *cls,
unsigned int http_status,
enum TALER_ErrorCode ec,
uint64_t serial_id,
const struct TALER_BANK_DebitDetails *details,
const json_t *json)
const struct TALER_BANK_DebitHistoryResponse *reply)
{
(void) cls;
dhh = NULL;
if (MHD_HTTP_OK != http_status)
switch (reply->http_status)
{
if ( (MHD_HTTP_NO_CONTENT != http_status) ||
(TALER_EC_NONE != ec) )
{
if (0 == http_status)
{
fprintf (stderr,
"Failed to obtain HTTP reply from `%s'\n",
auth.wire_gateway_url);
}
else
{
fprintf (stderr,
"Failed to obtain debit history from `%s': HTTP status %u (%s)\n",
auth.wire_gateway_url,
http_status,
TALER_ErrorCode_get_hint (ec));
}
if (NULL != json)
json_dumpf (json,
stderr,
JSON_INDENT (2));
global_ret = 2;
GNUNET_SCHEDULER_shutdown ();
return GNUNET_NO;
}
case 0:
fprintf (stderr,
"Failed to obtain HTTP reply from `%s'\n",
auth.wire_gateway_url);
global_ret = 2;
break;
case MHD_HTTP_NO_CONTENT:
fprintf (stdout,
"End of transactions list.\n");
"No transactions.\n");
global_ret = 0;
GNUNET_SCHEDULER_shutdown ();
return GNUNET_NO;
break;
case MHD_HTTP_OK:
for (unsigned int i = 0; i<reply->details.success.details_length; i++)
{
const struct TALER_BANK_DebitDetails *dd =
&reply->details.success.details[i];
/* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) &&
(0 != strcasecmp (credit_account,
dd->credit_account_uri) ) )
continue;
if ( (NULL != debit_account) &&
(0 != strcasecmp (debit_account,
dd->debit_account_uri) ) )
continue;
fprintf (stdout,
"%llu: %s->%s (%s) over %s at %s\n",
(unsigned long long) dd->serial_id,
dd->debit_account_uri,
dd->credit_account_uri,
TALER_B2S (&dd->wtid),
TALER_amount2s (&dd->amount),
GNUNET_TIME_timestamp2s (dd->execution_date));
}
global_ret = 0;
break;
default:
fprintf (stderr,
"Failed to obtain debit history from `%s': HTTP status %u (%s)\n",
auth.wire_gateway_url,
reply->http_status,
TALER_ErrorCode_get_hint (reply->ec));
if (NULL != reply->response)
json_dumpf (reply->response,
stderr,
JSON_INDENT (2));
global_ret = 2;
break;
}
/* If credit/debit accounts were specified, use as a filter */
if ( (NULL != credit_account) &&
(0 != strcasecmp (credit_account,
details->credit_account_uri) ) )
return GNUNET_OK;
if ( (NULL != debit_account) &&
(0 != strcasecmp (debit_account,
details->debit_account_uri) ) )
return GNUNET_OK;
fprintf (stdout,
"%llu: %s->%s (%s) over %s at %s\n",
(unsigned long long) serial_id,
details->debit_account_uri,
details->credit_account_uri,
TALER_B2S (&details->wtid),
TALER_amount2s (&details->amount),
GNUNET_TIME_timestamp2s (details->execution_date));
return GNUNET_OK;
GNUNET_SCHEDULER_shutdown ();
}

View File

@ -290,7 +290,6 @@ batch_deposit_transaction (void *cls,
mhd_ret);
if (qs < 0)
return qs;
qs = TEH_plugin->do_deposit (
TEH_plugin->cls,
deposit,

View File

@ -74,20 +74,20 @@ reply_deposit_success (
struct TALER_ExchangeSignatureP sig;
enum TALER_ErrorCode ec;
if (TALER_EC_NONE !=
(ec = TALER_exchange_online_deposit_confirmation_sign (
&TEH_keys_exchange_sign_,
h_contract_terms,
h_wire,
h_policy,
exchange_timestamp,
wire_deadline,
refund_deadline,
amount_without_fee,
coin_pub,
merchant,
&pub,
&sig)))
ec = TALER_exchange_online_deposit_confirmation_sign (
&TEH_keys_exchange_sign_,
h_contract_terms,
h_wire,
h_policy,
exchange_timestamp,
wire_deadline,
refund_deadline,
amount_without_fee,
coin_pub,
merchant,
&pub,
&sig);
if (TALER_EC_NONE != ec)
{
return TALER_MHD_reply_with_ec (connection,
ec,
@ -187,8 +187,6 @@ deposit_transaction (void *cls,
mhd_ret);
if (qs < 0)
return qs;
/* If the deposit has a policy associated to it, persist it. This will
* insert or update the record. */
if (dc->has_policy)
@ -203,16 +201,14 @@ deposit_transaction (void *cls,
if (qs < 0)
return qs;
}
qs = TEH_plugin->do_deposit (
TEH_plugin->cls,
dc->deposit,
dc->known_coin_id,
&dc->h_payto,
(dc->has_policy)
? &dc->policy_details_serial_id
: NULL,
? &dc->policy_details_serial_id
: NULL,
&dc->exchange_timestamp,
&balance_ok,
&in_conflict);

View File

@ -605,188 +605,193 @@ do_commit (struct WireAccount *wa)
}
/**
* We got incoming transaction details from the bank. Add them
* to the database.
*
* @param wa wire account we are handling
* @param details array of transaction details
* @param details_length length of the @a details array
* @return true on success
*/
static bool
process_reply (struct WireAccount *wa,
const struct TALER_BANK_CreditDetails *details,
unsigned int details_length)
{
uint64_t lroff = wa->latest_row_off;
/* check serial IDs for range constraints */
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
if (cd->serial_id < lroff)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Serial ID %llu not monotonic (got %llu before). Failing!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) lroff);
db_plugin->rollback (db_plugin->cls);
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return false;
}
if (cd->serial_id >= wa->max_row_off)
{
/* We got 'limit' transactions back from the bank, so we should not
introduce any delay before the next call. */
wa->delay = false;
}
if (cd->serial_id > wa->shard_end)
{
/* we are *past* the current shard (likely because the serial_id of the
shard_end happens to not exist in the DB). So commit and stop this
iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serial ID %llu past shard end at %llu, ending iteration early!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) wa->shard_end);
details_length = i;
wa->delay = false;
break;
}
lroff = cd->serial_id;
}
if (0 == details_length)
{
/* Server should have used 204, not 200! */
GNUNET_break_op (0);
return true;
}
if (GNUNET_OK !=
db_plugin->start_read_committed (db_plugin->cls,
"wirewatch check for incoming wire transfers"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return false;
}
wa->started_transaction = true;
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
enum GNUNET_DB_QueryStatus qs;
/* FIXME #7276: Consider using Postgres multi-valued insert here,
for up to 15x speed-up according to
https://dba.stackexchange.com/questions/224989/multi-row-insert-vs-transactional-single-row-inserts#225006
(Note: this may require changing both the
plugin API as well as modifying how this function is called.) */
qs = db_plugin->reserves_in_insert (db_plugin->cls,
&cd->reserve_pub,
&cd->amount,
cd->execution_date,
cd->debit_account_uri,
wa->ai->section_name,
cd->serial_id);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls);
wa->started_transaction = false;
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return false;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for reserves_in_insert. Rolling back.\n");
handle_soft_error (wa);
wa->hh = NULL;
return true;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* Either wirewatch was freshly started after the system was
shutdown and we're going over an incomplete shard again
after being restarted, or the shard lock period was too
short (number of workers set incorrectly?) and a 2nd
wirewatcher has been stealing our work while we are still
at it. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Attempted to import transaction %llu (%s) twice. "
"This should happen rarely (if not, ask for support).\n",
(unsigned long long) cd->serial_id,
wa->job_name);
db_plugin->rollback (db_plugin->cls);
wa->latest_row_off = cd->serial_id;
wa->started_transaction = false;
/* already existed, ok, let's just continue */
return true;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
wa->latest_row_off = cd->serial_id;
/* normal case */
break;
}
}
do_commit (wa);
if (check_shard_done (wa))
account_completed (wa);
else
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
wa);
return true;
}
/**
* Callbacks of this type are used to serve the result of asking
* the bank for the transaction history.
*
* @param cls closure with the `struct WioreAccount *` we are processing
* @param http_status HTTP status code from the server
* @param ec taler error code
* @param serial_id identification of the position at which we are querying
* @param details details about the wire transfer
* @param json raw JSON response
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
* @param cls closure with the `struct WireAccount *` we are processing
* @param reply response we got from the bank
*/
static enum GNUNET_GenericReturnValue
static void
history_cb (void *cls,
unsigned int http_status,
enum TALER_ErrorCode ec,
uint64_t serial_id,
const struct TALER_BANK_CreditDetails *details,
const json_t *json)
const struct TALER_BANK_CreditHistoryResponse *reply)
{
struct WireAccount *wa = cls;
enum GNUNET_DB_QueryStatus qs;
bool ok;
(void) json;
GNUNET_assert (NULL == task);
if (NULL == details)
wa->hh = NULL;
switch (reply->http_status)
{
wa->hh = NULL;
if ( (! ( (MHD_HTTP_NOT_FOUND == http_status) &&
(ignore_account_404) ) ) &&
( (MHD_HTTP_NO_CONTENT != http_status) &&
( (TALER_EC_NONE != ec) ||
(MHD_HTTP_OK != http_status) ) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Error fetching history: %s (%u)\n",
TALER_ErrorCode_get_hint (ec),
http_status);
if (! (exit_on_error || test_mode) )
{
account_completed (wa);
return GNUNET_OK;
}
GNUNET_SCHEDULER_shutdown ();
return GNUNET_OK;
}
if (wa->started_transaction)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"End of list. Committing progress on %s of (%llu,%llu]!\n",
wa->job_name,
(unsigned long long) wa->batch_start,
(unsigned long long) wa->latest_row_off);
do_commit (wa);
return GNUNET_OK; /* will be ignored anyway */
}
/* We did not even start a transaction. */
if ( (wa->delay) &&
(test_mode) &&
(NULL == wa->next) )
{
/* We exit on idle */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shutdown due to test mode!\n");
GNUNET_SCHEDULER_shutdown ();
return GNUNET_OK;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No transactions in history response, moving on.\n");
account_completed (wa);
return GNUNET_OK; /* will be ignored anyway */
case 0:
ok = false;
case MHD_HTTP_OK:
ok = process_reply (wa,
reply->details.success.details,
reply->details.success.details_length);
break;
case MHD_HTTP_NO_CONTENT:
ok = true;
break;
case MHD_HTTP_NOT_FOUND:
ok = ignore_account_404;
break;
default:
ok = false;
break;
}
/* We did get 'details' from the bank. Do sanity checks before inserting. */
if (serial_id < wa->latest_row_off)
if (! ok)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Serial ID %llu not monotonic (got %llu before). Failing!\n",
(unsigned long long) serial_id,
(unsigned long long) wa->latest_row_off);
"Error fetching history: %s (%u)\n",
TALER_ErrorCode_get_hint (reply->ec),
reply->http_status);
if (! (exit_on_error || test_mode) )
{
account_completed (wa);
return;
}
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return GNUNET_SYSERR;
return;
}
/* If we got 'limit' transactions back from the bank,
we should not introduce any delay before the next
call. */
if (serial_id >= wa->max_row_off)
wa->delay = false;
if (serial_id > wa->shard_end)
{
/* we are *past* the current shard (likely because the serial_id of the
shard_end happens to not exist in the DB). So commit and stop this
iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serial ID %llu past shard end at %llu, ending iteration early!\n",
(unsigned long long) serial_id,
(unsigned long long) wa->shard_end);
wa->latest_row_off = serial_id - 1; /* excluding serial_id! */
wa->hh = NULL;
if (wa->started_transaction)
{
GNUNET_assert (NULL == task);
do_commit (wa);
}
else
{
GNUNET_assert (NULL == task);
if (check_shard_done (wa))
account_completed (wa);
else
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
wa);
}
return GNUNET_SYSERR;
}
if (! wa->started_transaction)
{
if (GNUNET_OK !=
db_plugin->start_read_committed (db_plugin->cls,
"wirewatch check for incoming wire transfers"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return GNUNET_SYSERR;
}
wa->started_transaction = true;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Adding wire transfer over %s with (hashed) subject `%s'\n",
TALER_amount2s (&details->amount),
TALER_B2S (&details->reserve_pub));
/* FIXME #7276: Consider using Postgres multi-valued insert here,
for up to 15x speed-up according to
https://dba.stackexchange.com/questions/224989/multi-row-insert-vs-transactional-single-row-inserts#225006
(Note: this may require changing both the
plugin API as well as modifying how this function is called.) */
qs = db_plugin->reserves_in_insert (db_plugin->cls,
&details->reserve_pub,
&details->amount,
details->execution_date,
details->debit_account_uri,
wa->ai->section_name,
serial_id);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls);
wa->started_transaction = false;
GNUNET_SCHEDULER_shutdown ();
wa->hh = NULL;
return GNUNET_SYSERR;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for reserves_in_insert. Rolling back.\n");
handle_soft_error (wa);
wa->hh = NULL;
return GNUNET_SYSERR;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* Either wirewatch was freshly started after the system was
shutdown and we're going over an incomplete shard again
after being restarted, or the shard lock period was too
short (number of workers set incorrectly?) and a 2nd
wirewatcher has been stealing our work while we are still
at it. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Attempted to import transaction %llu (%s) twice. "
"This should happen rarely (if not, ask for support).\n",
(unsigned long long) serial_id,
wa->job_name);
/* already existed, ok, let's just continue */
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* normal case */
break;
}
wa->latest_row_off = serial_id;
return GNUNET_OK;
}

View File

@ -26,18 +26,20 @@
#include "pg_helper.h"
#include "pg_setup_wire_target.h"
#include "pg_compute_shard.h"
enum GNUNET_DB_QueryStatus
TEH_PG_insert_deposit (void *cls,
struct GNUNET_TIME_Timestamp exchange_timestamp,
const struct TALER_EXCHANGEDB_Deposit *deposit)
struct GNUNET_TIME_Timestamp exchange_timestamp,
const struct TALER_EXCHANGEDB_Deposit *deposit)
{
struct PostgresClosure *pg = cls;
struct TALER_PaytoHashP h_payto;
enum GNUNET_DB_QueryStatus qs;
qs = TEH_PG_setup_wire_target (pg,
deposit->receiver_wire_account,
&h_payto);
deposit->receiver_wire_account,
&h_payto);
if (qs < 0)
return qs;
if (GNUNET_TIME_timestamp_cmp (deposit->wire_deadline,

View File

@ -260,6 +260,11 @@ struct TALER_BANK_CreditHistoryHandle;
*/
struct TALER_BANK_CreditDetails
{
/**
* Serial ID of the wire transfer.
*/
uint64_t serial_id;
/**
* Amount that was transferred
*/
@ -271,49 +276,85 @@ struct TALER_BANK_CreditDetails
struct GNUNET_TIME_Timestamp execution_date;
/**
* Reserve public key encoded in the wire
* transfer subject.
* Reserve public key encoded in the wire transfer subject.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* payto://-URL of the source account that
* send the funds.
* payto://-URL of the source account that send the funds.
*/
const char *debit_account_uri;
/**
* payto://-URL of the target account that
* received the funds.
* payto://-URL of the target account that received the funds.
*/
const char *credit_account_uri;
};
/**
* Response details for a history request.
*/
struct TALER_BANK_CreditHistoryResponse
{
/**
* HTTP status. Note that #MHD_HTTP_OK and #MHD_HTTP_NO_CONTENT are both
* successful replies, but @e details will only contain @e success information
* if this is set to #MHD_HTTP_OK.
*/
unsigned int http_status;
/**
* Taler error code, #TALER_EC_NONE on success.
*/
enum TALER_ErrorCode ec;
/**
* Full response, NULL if body was not in JSON format.
*/
const json_t *response;
/**
* Details returned depending on the @e http_status.
*/
union
{
/**
* Details if status was #MHD_HTTP_OK
*/
struct
{
/**
* Array of transactions recevied.
*/
const struct TALER_BANK_CreditDetails *details;
/**
* Length of the @e details array.
*/
unsigned int details_length;
} success;
} details;
};
/**
* Callbacks of this type are used to serve the result of asking
* the bank for the credit transaction history.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
* 0 if the bank's reply is bogus (fails to follow the protocol),
* #MHD_HTTP_NO_CONTENT if there are no more results; on success the
* last callback is always of this status (even if `abs(num_results)` were
* already returned).
* @param ec detailed error code
* @param serial_id monotonically increasing counter corresponding to the transaction
* @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
* @param reply details about the response
*/
typedef enum GNUNET_GenericReturnValue
typedef void
(*TALER_BANK_CreditHistoryCallback)(
void *cls,
unsigned int http_status,
enum TALER_ErrorCode ec,
uint64_t serial_id,
const struct TALER_BANK_CreditDetails *details,
const json_t *json);
const struct TALER_BANK_CreditHistoryResponse *reply);
/**
@ -369,6 +410,11 @@ struct TALER_BANK_DebitHistoryHandle;
*/
struct TALER_BANK_DebitDetails
{
/**
* Serial ID of the wire transfer.
*/
uint64_t serial_id;
/**
* Amount that was transferred
*/
@ -390,44 +436,81 @@ struct TALER_BANK_DebitDetails
const char *exchange_base_url;
/**
* payto://-URI of the source account that
* send the funds.
* payto://-URI of the source account that send the funds.
*/
const char *debit_account_uri;
/**
* payto://-URI of the target account that
* received the funds.
* payto://-URI of the target account that received the funds.
*/
const char *credit_account_uri;
};
/**
* Response details for a history request.
*/
struct TALER_BANK_DebitHistoryResponse
{
/**
* HTTP status. Note that #MHD_HTTP_OK and #MHD_HTTP_NO_CONTENT are both
* successful replies, but @e details will only contain @e success information
* if this is set to #MHD_HTTP_OK.
*/
unsigned int http_status;
/**
* Taler error code, #TALER_EC_NONE on success.
*/
enum TALER_ErrorCode ec;
/**
* Full response, NULL if body was not in JSON format.
*/
const json_t *response;
/**
* Details returned depending on the @e http_status.
*/
union
{
/**
* Details if status was #MHD_HTTP_OK
*/
struct
{
/**
* Array of transactions initiated.
*/
const struct TALER_BANK_DebitDetails *details;
/**
* Length of the @e details array.
*/
unsigned int details_length;
} success;
} details;
};
/**
* Callbacks of this type are used to serve the result of asking
* the bank for the debit transaction history.
*
* @param cls closure
* @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
* 0 if the bank's reply is bogus (fails to follow the protocol),
* #MHD_HTTP_NO_CONTENT if there are no more results; on success the
* last callback is always of this status (even if `abs(num_results)` were
* already returned).
* @param ec detailed error code
* @param serial_id monotonically increasing counter corresponding to the transaction
* @param details details about the wire transfer
* @param json detailed response from the HTTPD, or NULL if reply was not in JSON
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
* @param reply details about the response
*/
typedef enum GNUNET_GenericReturnValue
typedef void
(*TALER_BANK_DebitHistoryCallback)(
void *cls,
unsigned int http_status,
enum TALER_ErrorCode ec,
uint64_t serial_id,
const struct TALER_BANK_DebitDetails *details,
const json_t *json);
const struct TALER_BANK_DebitHistoryResponse *reply);
/**