From 741831e87b3b189d3f75bdd9fa2ccc9a8d2ba8e2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 17 Nov 2022 13:28:15 +0100 Subject: [PATCH] bank API refactoring for #7276 (incomplete) --- src/bank-lib/bank_api_credit.c | 120 +++---- src/bank-lib/bank_api_debit.c | 124 ++++--- .../taler-exchange-wire-gateway-client.c | 236 ++++++------ .../taler-exchange-httpd_batch-deposit.c | 1 - src/exchange/taler-exchange-httpd_deposit.c | 36 +- src/exchange/taler-exchange-wirewatch.c | 335 +++++++++--------- src/exchangedb/pg_insert_deposit.c | 10 +- src/include/taler_bank_service.h | 167 ++++++--- 8 files changed, 541 insertions(+), 488 deletions(-) diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c index 36cab3d51..127ae057d 100644 --- a/src/bank-lib/bank_api_credit.c +++ b/src/bank-lib/bank_api_credit.c @@ -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; iamount), + 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); } diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c index 2a76495bd..6642dda81 100644 --- a/src/bank-lib/bank_api_debit.c +++ b/src/bank-lib/bank_api_debit.c @@ -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; iamount), + 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); } diff --git a/src/bank-lib/taler-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c index ab16573a7..5bfd9311d 100644 --- a/src/bank-lib/taler-exchange-wire-gateway-client.c +++ b/src/bank-lib/taler-exchange-wire-gateway-client.c @@ -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; idetails.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; idetails.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 (); } diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c index 7a3ea0fa4..0545c393b 100644 --- a/src/exchange/taler-exchange-httpd_batch-deposit.c +++ b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -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, diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 455888a89..740db7c1f 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -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); diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 146f2ee72..d84344fc8 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -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; iserial_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; ireserves_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; } diff --git a/src/exchangedb/pg_insert_deposit.c b/src/exchangedb/pg_insert_deposit.c index 09247e282..ec4d49bf9 100644 --- a/src/exchangedb/pg_insert_deposit.c +++ b/src/exchangedb/pg_insert_deposit.c @@ -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, diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h index bb7f3d33b..7b26bceab 100644 --- a/src/include/taler_bank_service.h +++ b/src/include/taler_bank_service.h @@ -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); /**