From 538ab8753c28c4767eacb49e04343411ed83932d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 1 Oct 2022 16:25:06 +0200 Subject: [PATCH 01/17] -implement first draft of testing_api_cmd_reserve_get_attestable.c --- contrib/gana | 2 +- src/testing/Makefile.am | 1 + .../testing_api_cmd_reserve_get_attestable.c | 81 +++++++++++++------ 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/contrib/gana b/contrib/gana index 9dee7d6e8..d402af78f 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 9dee7d6e8f967fdc58ae224e19ec03989ac35c52 +Subproject commit d402af78f6d360841db53baa46dddae13590ec33 diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 78d4cdced..82c786c35 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -86,6 +86,7 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_refund.c \ testing_api_cmd_refresh.c \ testing_api_cmd_reserve_get.c \ + testing_api_cmd_reserve_get_attestable.c \ testing_api_cmd_reserve_history.c \ testing_api_cmd_reserve_open.c \ testing_api_cmd_reserve_purse.c \ diff --git a/src/testing/testing_api_cmd_reserve_get_attestable.c b/src/testing/testing_api_cmd_reserve_get_attestable.c index c0cae832a..29c8f6a3a 100644 --- a/src/testing/testing_api_cmd_reserve_get_attestable.c +++ b/src/testing/testing_api_cmd_reserve_get_attestable.c @@ -41,17 +41,17 @@ struct GetAttestableState /** * Handle to the "reserve get_attestable" operation. */ - struct TALER_EXCHANGE_ReservesGetAttestableHandle *rsh; + struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah; /** - * Expected reserve balance. + * Expected attestable attributes. */ - const char *expected_balance; + const char **expected_attestables; /** - * Private key of the reserve being analyzed. + * Length of the @e expected_attestables array. */ - const struct TALER_ReservePrivateKeyP *reserve_priv; + unsigned int expected_attestables_length; /** * Public key of the reserve being analyzed. @@ -78,14 +78,14 @@ struct GetAttestableState * @param rs HTTP response details */ static void -reserve_get_attestable_cb (void *cls, - const struct TALER_EXCHANGE_ReserveGetAttestable *rs) +reserve_get_attestable_cb ( + void *cls, + const struct TALER_EXCHANGE_ReserveGetAttestResult *rs) { struct GetAttestableState *ss = cls; struct TALER_TESTING_Interpreter *is = ss->is; - struct TALER_Amount eb; - ss->rsh = NULL; + ss->rgah = NULL; if (ss->expected_response_code != rs->hr.http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -104,6 +104,7 @@ reserve_get_attestable_cb (void *cls, TALER_TESTING_interpreter_next (is); return; } + // FIXME: check returned list matches expectations! TALER_TESTING_interpreter_next (is); } @@ -122,6 +123,8 @@ get_attestable_run (void *cls, { struct GetAttestableState *ss = cls; const struct TALER_TESTING_Command *create_reserve; + const struct TALER_ReservePrivateKeyP *reserve_priv; + const struct TALER_ReservePublicKeyP *reserve_pub; ss->is = is; create_reserve @@ -134,21 +137,31 @@ get_attestable_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } - if (GNUNET_OK != + if (GNUNET_OK == TALER_TESTING_get_trait_reserve_priv (create_reserve, - &ss->reserve_priv)) + &reserve_priv)) { - GNUNET_break (0); - TALER_LOG_ERROR ("Failed to find reserve_priv for get_attestable query\n"); - TALER_TESTING_interpreter_fail (is); - return; + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &ss->reserve_pub.eddsa_pub); } - GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv, - &ss->reserve_pub.eddsa_pub); - ss->rsh = TALER_EXCHANGE_reserves_get_attestable (is->exchange, - ss->reserve_priv, - &reserve_get_attestable_cb, - ss); + else + { + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_pub (create_reserve, + &reserve_pub)) + { + GNUNET_break (0); + TALER_LOG_ERROR ( + "Failed to find reserve_priv for get_attestable query\n"); + TALER_TESTING_interpreter_fail (is); + return; + } + ss->reserve_pub = *reserve_pub; + } + ss->rgah = TALER_EXCHANGE_reserves_get_attestable (is->exchange, + &ss->reserve_pub, + &reserve_get_attestable_cb, + ss); } @@ -165,15 +178,16 @@ get_attestable_cleanup (void *cls, { struct GetAttestableState *ss = cls; - if (NULL != ss->rsh) + if (NULL != ss->rgah) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command %u (%s) did not complete\n", ss->is->ip, cmd->label); - TALER_EXCHANGE_reserves_get_attestable_cancel (ss->rsh); - ss->rsh = NULL; + TALER_EXCHANGE_reserves_get_attestable_cancel (ss->rgah); + ss->rgah = NULL; } + GNUNET_free (ss->expected_attestables); GNUNET_free (ss); } @@ -185,12 +199,29 @@ TALER_TESTING_cmd_reserve_get_attestable (const char *label, ...) { struct GetAttestableState *ss; + va_list ap; + unsigned int num_expected; + const char *ea; + + num_expected = 0; + va_start (ap, expected_response_code); + while (NULL != va_arg (ap, const char *)) + num_expected++; + va_end (ap); GNUNET_assert (NULL != reserve_reference); ss = GNUNET_new (struct GetAttestableState); ss->reserve_reference = reserve_reference; - ss->expected_balance = expected_balance; ss->expected_response_code = expected_response_code; + ss->expected_attestables_length = num_expected; + ss->expected_attestables = GNUNET_new_array (num_expected, + const char *); + num_expected = 0; + va_start (ap, expected_response_code); + while (NULL != (ea = va_arg (ap, const char *))) + ss->expected_attestables[num_expected++] = ea; + va_end (ap); + { struct TALER_TESTING_Command cmd = { .cls = ss, From 9cba7d4c3e03bb648d81f516faeab8d0782728c8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 1 Oct 2022 16:30:22 +0200 Subject: [PATCH 02/17] -implement first draft of testing_api_cmd_reserve_attest.c --- src/testing/Makefile.am | 1 + src/testing/testing_api_cmd_reserve_attest.c | 45 +++++++++++++++----- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 82c786c35..256276ce6 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -85,6 +85,7 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_recoup_refresh.c \ testing_api_cmd_refund.c \ testing_api_cmd_refresh.c \ + testing_api_cmd_reserve_attest.c \ testing_api_cmd_reserve_get.c \ testing_api_cmd_reserve_get_attestable.c \ testing_api_cmd_reserve_history.c \ diff --git a/src/testing/testing_api_cmd_reserve_attest.c b/src/testing/testing_api_cmd_reserve_attest.c index 8ed959249..5ed25c13c 100644 --- a/src/testing/testing_api_cmd_reserve_attest.c +++ b/src/testing/testing_api_cmd_reserve_attest.c @@ -26,7 +26,6 @@ #include #include "taler_testing_lib.h" - /** * State for a "attest" CMD. */ @@ -43,11 +42,6 @@ struct AttestState */ struct TALER_EXCHANGE_ReservesAttestHandle *rsh; - /** - * Expected reserve balance. - */ - const char *expected_balance; - /** * Private key of the reserve being analyzed. */ @@ -58,6 +52,16 @@ struct AttestState */ struct TALER_ReservePublicKeyP reserve_pub; + /** + * Array of attributes to request, of length @e attrs_len. + */ + const char **attrs; + + /** + * Length of the @e attrs array. + */ + unsigned int attrs_len; + /** * Expected HTTP response code. */ @@ -78,12 +82,12 @@ struct AttestState * @param rs HTTP response details */ static void -reserve_attest_cb (void *cls, - const struct TALER_EXCHANGE_ReserveAttest *rs) +reserve_attest_cb ( + void *cls, + const struct TALER_EXCHANGE_ReservePostAttestResult *rs) { struct AttestState *ss = cls; struct TALER_TESTING_Interpreter *is = ss->is; - struct TALER_Amount eb; ss->rsh = NULL; if (ss->expected_response_code != rs->hr.http_status) @@ -104,6 +108,7 @@ reserve_attest_cb (void *cls, TALER_TESTING_interpreter_next (is); return; } + /* FIXME: persist attestation... */ TALER_TESTING_interpreter_next (is); } @@ -147,6 +152,8 @@ attest_run (void *cls, &ss->reserve_pub.eddsa_pub); ss->rsh = TALER_EXCHANGE_reserves_attest (is->exchange, ss->reserve_priv, + ss->attrs_len, + ss->attrs, &reserve_attest_cb, ss); } @@ -174,6 +181,7 @@ attest_cleanup (void *cls, TALER_EXCHANGE_reserves_attest_cancel (ss->rsh); ss->rsh = NULL; } + GNUNET_free (ss->attrs); GNUNET_free (ss); } @@ -185,12 +193,29 @@ TALER_TESTING_cmd_reserve_attest (const char *label, ...) { struct AttestState *ss; + unsigned int num_args; + const char *ea; + va_list ap; + + num_args = 0; + va_start (ap, expected_response_code); + while (NULL != va_arg (ap, const char *)) + num_args++; + va_end (ap); GNUNET_assert (NULL != reserve_reference); ss = GNUNET_new (struct AttestState); ss->reserve_reference = reserve_reference; - ss->expected_balance = expected_balance; ss->expected_response_code = expected_response_code; + ss->attrs_len = num_args; + ss->attrs = GNUNET_new_array (num_args, + const char *); + num_args = 0; + va_start (ap, expected_response_code); + while (NULL != (ea = va_arg (ap, const char *))) + ss->attrs[num_args++] = ea; + va_end (ap); + { struct TALER_TESTING_Command cmd = { .cls = ss, From 442b2116ed8d1bcd00df16598ca14ea8582c94b4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 1 Oct 2022 16:32:58 +0200 Subject: [PATCH 03/17] -already done, remove finished FIXME --- src/lib/exchange_api_reserves_attest.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/exchange_api_reserves_attest.c b/src/lib/exchange_api_reserves_attest.c index 5ccc18e5a..a7a89a2ef 100644 --- a/src/lib/exchange_api_reserves_attest.c +++ b/src/lib/exchange_api_reserves_attest.c @@ -131,8 +131,6 @@ handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh, GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - // FIXME: verify exchange signature! - // => needs crypto API first! rsh->cb (rsh->cb_cls, &rs); rsh->cb = NULL; From 7bf0f2a43d42b168beaee8fbbafe51386f1127b1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 1 Oct 2022 16:35:13 +0200 Subject: [PATCH 04/17] -doxygen --- src/include/taler_crypto_lib.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index b5ae20832..09e386cd4 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -3223,7 +3223,7 @@ TALER_wallet_reserve_attest_request_sign ( * * @param request_timestamp when was the request created * @param details which attributes are requested - * @param reserve_priv private key of the reserve + * @param reserve_pub public key of the reserve * @param reserve_sig where to store the signature * @return #GNUNET_OK if the signature is valid */ @@ -4291,7 +4291,6 @@ TALER_exchange_online_reserve_attest_details_sign ( * Verify signature by exchange affirming that a reserve * has had certain attributes verified via KYC. * - * @param scb function to call to create the signature * @param attest_timestamp our time * @param expiration_time when does the KYC data expire * @param reserve_pub for which reserve are attributes attested From 2f1fb32e1c51b05361ddf3321c316e3d8c65ebfe Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 1 Oct 2022 23:06:24 +0200 Subject: [PATCH 05/17] -skeletons for reserve control endpoints --- contrib/uncrustify.sh | 2 +- .../taler-exchange-httpd_reserves_attest.c | 295 +++++++++++++++ .../taler-exchange-httpd_reserves_attest.h | 41 +++ .../taler-exchange-httpd_reserves_close.c | 343 ++++++++++++++++++ .../taler-exchange-httpd_reserves_close.h | 41 +++ ...taler-exchange-httpd_reserves_get_attest.c | 146 ++++++++ .../taler-exchange-httpd_reserves_history.h | 5 +- .../taler-exchange-httpd_reserves_open.c | 281 ++++++++++++++ .../taler-exchange-httpd_reserves_open.h | 41 +++ 9 files changed, 1191 insertions(+), 4 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_reserves_attest.c create mode 100644 src/exchange/taler-exchange-httpd_reserves_attest.h create mode 100644 src/exchange/taler-exchange-httpd_reserves_close.c create mode 100644 src/exchange/taler-exchange-httpd_reserves_close.h create mode 100644 src/exchange/taler-exchange-httpd_reserves_get_attest.c create mode 100644 src/exchange/taler-exchange-httpd_reserves_open.c create mode 100644 src/exchange/taler-exchange-httpd_reserves_open.h diff --git a/contrib/uncrustify.sh b/contrib/uncrustify.sh index 09abaf54e..e8e05d3e7 100755 --- a/contrib/uncrustify.sh +++ b/contrib/uncrustify.sh @@ -9,6 +9,6 @@ if ! uncrustify --version >/dev/null; then exit 1 fi -find "$DIR/../src" \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) | grep -v mustach \ +find "$DIR/../src" \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) \ -exec uncrustify -c "$DIR/uncrustify.cfg" --replace --no-backup {} + \ || true diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c new file mode 100644 index 000000000..94399f076 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_attest.c @@ -0,0 +1,295 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_close.c + * @brief Handle /reserves/$RESERVE_PUB/close requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_mhd_lib.h" +#include "taler_json_lib.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_reserves_attest.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * How far do we allow a client's time to be off when + * checking the request timestamp? + */ +#define TIMESTAMP_TOLERANCE \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + + +/** + * Closure for #reserve_attest_transaction. + */ +struct ReserveAttestContext +{ + /** + * Public key of the reserve the inquiry is about. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Timestamp of the request. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Client signature approving the request. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Attest of the reserve, set in the callback. + */ + struct TALER_EXCHANGEDB_ReserveAttest *rh; + + /** + * Global fees applying to the request. + */ + const struct TEH_GlobalFee *gf; + + /** + * Current reserve balance. + */ + struct TALER_Amount balance; +}; + + +/** + * Send reserve attest to client. + * + * @param connection connection to the client + * @param rhc reserve attest to return + * @return MHD result code + */ +static MHD_RESULT +reply_reserve_attest_success (struct MHD_Connection *connection, + const struct ReserveAttestContext *rhc) +{ + const struct TALER_EXCHANGEDB_ReserveAttest *rh = rhc->rh; + json_t *json_attest; + + json_attest = TEH_RESPONSE_compile_reserve_attest (rh); + if (NULL == json_attest) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, + NULL); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + TALER_JSON_pack_amount ("balance", + &rhc->balance), + GNUNET_JSON_pack_array_steal ("attest", + json_attest)); +} + + +/** + * Function implementing /reserves/$RID/attest transaction. Given the public + * key of a reserve, return the associated transaction attest. Runs the + * transaction logic; IF it returns a non-error code, the transaction logic + * MUST NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it + * returns the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls a `struct ReserveAttestContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!); unused + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +reserve_attest_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct ReserveAttestContext *rsc = cls; + enum GNUNET_DB_QueryStatus qs; + + if (! TALER_amount_is_zero (&rsc->gf->fees.attest)) + { + bool balance_ok = false; + bool idempotent = true; + + qs = TEH_plugin->insert_attest_request (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->reserve_sig, + rsc->timestamp, + &rsc->gf->fees.attest, + &balance_ok, + &idempotent); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_attest"); + } + if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (! balance_ok) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_WITHDRAW_ATTEST_ERROR_INSUFFICIENT_FUNDS, + NULL); + } + if (idempotent) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Idempotent /reserves/attest request observed. Is caching working?\n"); + } + } + qs = TEH_plugin->get_reserve_attest (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->balance, + &rsc->rh); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_attest"); + } + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_attest (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + struct ReserveAttestContext rsc; + MHD_RESULT mhd_ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rsc.timestamp), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rsc.reserve_sig), + GNUNET_JSON_spec_end () + }; + struct GNUNET_TIME_Timestamp now; + + rsc.reserve_pub = reserve_pub; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + } + now = GNUNET_TIME_timestamp_get (); + if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, + rsc.timestamp.abs_time, + TIMESTAMP_TOLERANCE)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, + NULL); + } + { + struct TEH_KeyStateHandle *keys; + + keys = TEH_keys_get_state (); + if (NULL == keys) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + } + rsc.gf = TEH_keys_global_fee_by_time (keys, + rsc.timestamp); + } + if (NULL == rsc.gf) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, + NULL); + } + if (GNUNET_OK != + TALER_wallet_reserve_attest_verify (rsc.timestamp, + &rsc.gf->fees.attest, + reserve_pub, + &rsc.reserve_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, + NULL); + } + rsc.rh = NULL; + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "get reserve attest", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_attest_transaction, + &rsc)) + { + return mhd_ret; + } + if (NULL == rsc.rh) + { + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + NULL); + } + mhd_ret = reply_reserve_attest_success (rc->connection, + &rsc); + TEH_plugin->free_reserve_attest (TEH_plugin->cls, + rsc.rh); + return mhd_ret; +} + + +/* end of taler-exchange-httpd_reserves_attest.c */ diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.h b/src/exchange/taler-exchange-httpd_reserves_attest.h new file mode 100644 index 000000000..d5ec8fd70 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_attest.h @@ -0,0 +1,41 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_attest.h + * @brief Handle /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H +#define TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a POST "/reserves/$RID/attest" request. + * + * @param rc request context + * @param reserve_pub public key of the reserve + * @param root uploaded body from the client + * @return MHD result code + */ +MHD_RESULT +TEH_handler_reserves_attest (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + +#endif diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c new file mode 100644 index 000000000..286669240 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -0,0 +1,343 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_close.c + * @brief Handle /reserves/$RESERVE_PUB/close requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_mhd_lib.h" +#include "taler_json_lib.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_reserves_close.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * How far do we allow a client's time to be off when + * checking the request timestamp? + */ +#define TIMESTAMP_TOLERANCE \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + + +/** + * Closure for #reserve_close_transaction. + */ +struct ReserveCloseContext +{ + /** + * Public key of the reserve the inquiry is about. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Timestamp of the request. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Client signature approving the request. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Global fees applying to the request. + */ + const struct TEH_GlobalFee *gf; + + /** + * Amount that will be wired (after closing fees). + */ + struct TALER_Amount wire_amount; + + /** + * Where to wire the funds, may be NULL. + */ + const char *payto_uri; + + /** + * Hash of the @e payto_uri. + */ + struct TALER_PaytoHashP h_payto; + +}; + + +/** + * Send reserve close to client. + * + * @param connection connection to the client + * @param rhc reserve close to return + * @return MHD result code + */ +static MHD_RESULT +reply_reserve_close_success (struct MHD_Connection *connection, + const struct ReserveCloseContext *rhc) +{ + const struct TALER_EXCHANGEDB_ReserveClose *rh = rhc->rh; + + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + TALER_JSON_pack_amount ("wire_amount", + &rhc->wire_amount)); +} + + +/** + * Function implementing /reserves/$RID/close transaction. Given the public + * key of a reserve, return the associated transaction close. Runs the + * transaction logic; IF it returns a non-error code, the transaction logic + * MUST NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it + * returns the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls a `struct ReserveCloseContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!); unused + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +reserve_close_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct ReserveCloseContext *rcc = cls; + enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount balance; + char *payto_uri; + + qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, + rcc->reserve_pub, + &balance, + &payto_uri); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_balance"); + return qs; + case GNUNET_DB_STATUS_SOFT_ERROR: + return qs; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + *mhd_ret + = TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + if ( (NULL == rcc->payto_uri) && + (NULL == payto_uri) ) + { + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_RESERVE_CLOSE_NO_TARGET_ACCOUNT, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + if ( (NULL != rcc->payto_uri) && + ( (NULL == payto_uri) || + (0 != strcmp (payto_uri, + rcc->payto_uri)) ) ) + { + struct TALER_EXCHANGEDB_KycStatus kyc; + struct TALER_PaytoHashP kyc_payto; + + /* FIXME: also fetch KYC status from reserve + in query above, and if payto_uri specified + and KYC not yet done (check KYC triggers!), + fail with 451 kyc required! */ + *mhd_ret + = TEH_RESPONSE_reply_kyc_required (rcc->connection, + &kyc_payto, + &kyc); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + if (NULL == rcc->payto_uri) + rcc->payto_uri = payto_uri; + + if (0 > + TALER_amount_subtract (&rcc->wire_amount, + &balance, + &rcc->gf->fees.close)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client attempted to close reserve with insufficient balance.\n"); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &rcc->wire_amount)); + *mhd_ret = reply_reserve_close_success (rc->connection, + &rcc); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + { + qs = TEH_plugin->insert_close_request (TEH_plugin->cls, + rcc->reserve_pub, + payto_uri, + &rcc->reserve_sig, + rcc->timestamp, + &rcc->gf->fees.close, + &rcc->wire_amount); + GNUNET_free (payto_uri); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "insert_close_request"); + return qs; + } + if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + } + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_close (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + struct ReserveCloseContext rcc = { + .payto_uri = NULL, + .reserve_pub = reserve_pub + }; + MHD_RESULT mhd_ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rcc.timestamp), + GNUNET_JSON_spec_allow_null ( + GNUNET_JSON_spec_string ("payto_uri", + &rcc.payto_uri), + NULL), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rcc.reserve_sig), + GNUNET_JSON_spec_end () + }; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + } + + { + struct GNUNET_TIME_Timestamp now; + + now = GNUNET_TIME_timestamp_get (); + if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, + rcc.timestamp.abs_time, + TIMESTAMP_TOLERANCE)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, + NULL); + } + } + + { + struct TEH_KeyStateHandle *keys; + + keys = TEH_keys_get_state (); + if (NULL == keys) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + } + rcc.gf = TEH_keys_global_fee_by_time (keys, + rcc.timestamp); + } + if (NULL == rcc.gf) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, + NULL); + } + if (NULL != rcc.payto_uri) + TALER_payto_hash (&rcc.payto_uri, + &rcc.h_payto); + if (GNUNET_OK != + TALER_wallet_reserve_close_verify (rcc.timestamp, + &rcc.h_payto, + reserve_pub, + &rcc.reserve_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE, + NULL); + } + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "reserve close", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_close_transaction, + &rcc)) + { + return mhd_ret; + } + return reply_reserve_close_success (rc->connection, + &rcc); +} + + +/* end of taler-exchange-httpd_reserves_close.c */ diff --git a/src/exchange/taler-exchange-httpd_reserves_close.h b/src/exchange/taler-exchange-httpd_reserves_close.h new file mode 100644 index 000000000..4c70b17cb --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_close.h @@ -0,0 +1,41 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_close.h + * @brief Handle /reserves/$RESERVE_PUB/close requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H +#define TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a POST "/reserves/$RID/close" request. + * + * @param rc request context + * @param reserve_pub public key of the reserve + * @param root uploaded body from the client + * @return MHD result code + */ +MHD_RESULT +TEH_handler_reserves_close (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + +#endif diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c new file mode 100644 index 000000000..7d98babc0 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -0,0 +1,146 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_get_attest.c + * @brief Handle GET /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_mhd_lib.h" +#include "taler_json_lib.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_reserves_get_attest.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * Closure for #reserve_attest_transaction. + */ +struct ReserveAttestContext +{ + /** + * Public key of the reserve the inquiry is about. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Available attributes. + */ + json_t *attributes; + + /** + * Set to true if we did not find the reserve. + */ + bool not_found; +}; + + +/** + * Function implementing GET /reserves/$RID/attest transaction. + * Execute a /reserves/ get attest. Given the public key of a reserve, + * return the associated transaction attest. Runs the + * transaction logic; IF it returns a non-error code, the transaction + * logic MUST NOT queue a MHD response. IF it returns an hard error, + * the transaction logic MUST queue a MHD response and set @a mhd_ret. + * IF it returns the soft error code, the function MAY be called again + * to retry and MUST not queue a MHD response. + * + * @param cls a `struct ReserveAttestContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +reserve_attest_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct ReserveAttestContext *rsc = cls; + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_reserve_attributes (TEH_plugin->cls, + &rsc->reserve_pub, + &rsc->attributes); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_attributes"); + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + rsc->not_found = true; + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + rsc->not_found = false; + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, + const char *const args[1]) +{ + struct ReserveAttestContext rsc; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &rsc.reserve_pub, + sizeof (rsc.reserve_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED, + args[0]); + } + { + MHD_RESULT mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "get-attestable", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_attest_transaction, + &rsc)) + { + return mhd_ret; + } + } + /* generate proper response */ + if (rsc.not_found) + { + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + args[0]); + } + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + TALER_JSON_pack_object_steal ("attributes", + &rsc.attributes)); +} + + +/* end of taler-exchange-httpd_reserves_get_attest.c */ diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h b/src/exchange/taler-exchange-httpd_reserves_history.h index 9a2a93782..e02cb4d9b 100644 --- a/src/exchange/taler-exchange-httpd_reserves_history.h +++ b/src/exchange/taler-exchange-httpd_reserves_history.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -15,9 +15,8 @@ */ /** * @file taler-exchange-httpd_reserves_history.h - * @brief Handle /reserves/$RESERVE_PUB HISTORY requests + * @brief Handle /reserves/$RESERVE_PUB/history requests * @author Florian Dold - * @author Benedikt Mueller * @author Christian Grothoff */ #ifndef TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c new file mode 100644 index 000000000..9ef736f90 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -0,0 +1,281 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_open.c + * @brief Handle /reserves/$RESERVE_PUB/open requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_mhd_lib.h" +#include "taler_json_lib.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_reserves_open.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * How far do we allow a client's time to be off when + * checking the request timestamp? + */ +#define TIMESTAMP_TOLERANCE \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + + +/** + * Closure for #reserve_open_transaction. + */ +struct ReserveOpenContext +{ + /** + * Public key of the reserve the inquiry is about. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Timestamp of the request. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Client signature approving the request. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Open of the reserve, set in the callback. + */ + struct TALER_EXCHANGEDB_ReserveOpen *rh; + + /** + * Global fees applying to the request. + */ + const struct TEH_GlobalFee *gf; + + /** + * Current reserve balance. + */ + struct TALER_Amount balance; +}; + + +/** + * Send reserve open to client. + * + * @param connection connection to the client + * @param rhc reserve open to return + * @return MHD result code + */ +static MHD_RESULT +reply_reserve_open_success (struct MHD_Connection *connection, + const struct ReserveOpenContext *rhc) +{ + const struct TALER_EXCHANGEDB_ReserveOpen *rh = rhc->rh; + + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + TALER_JSON_pack_amount ("balance", + &rhc->balance)); +} + + +/** + * Function implementing /reserves/$RID/open transaction. Given the public + * key of a reserve, return the associated transaction open. Runs the + * transaction logic; IF it returns a non-error code, the transaction logic + * MUST NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it + * returns the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls a `struct ReserveOpenContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!); unused + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +reserve_open_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct ReserveOpenContext *rsc = cls; + enum GNUNET_DB_QueryStatus qs; + + if (! TALER_amount_is_zero (&rsc->gf->fees.open)) + { + bool balance_ok = false; + bool idempotent = true; + + qs = TEH_plugin->insert_open_request (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->reserve_sig, + rsc->timestamp, + &rsc->gf->fees.open, + &balance_ok, + &idempotent); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_open"); + } + if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (! balance_ok) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_WITHDRAW_OPEN_ERROR_INSUFFICIENT_FUNDS, + NULL); + } + if (idempotent) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Idempotent /reserves/open request observed. Is caching working?\n"); + } + } + qs = TEH_plugin->get_reserve_open (TEH_plugin->cls, + rsc->reserve_pub, + &rsc->balance, + &rsc->rh); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_reserve_open"); + } + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_open (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + struct ReserveOpenContext rsc; + MHD_RESULT mhd_ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rsc.timestamp), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rsc.reserve_sig), + GNUNET_JSON_spec_end () + }; + struct GNUNET_TIME_Timestamp now; + + rsc.reserve_pub = reserve_pub; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + } + now = GNUNET_TIME_timestamp_get (); + if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, + rsc.timestamp.abs_time, + TIMESTAMP_TOLERANCE)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, + NULL); + } + { + struct TEH_KeyStateHandle *keys; + + keys = TEH_keys_get_state (); + if (NULL == keys) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + } + rsc.gf = TEH_keys_global_fee_by_time (keys, + rsc.timestamp); + } + if (NULL == rsc.gf) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, + NULL); + } + if (GNUNET_OK != + TALER_wallet_reserve_open_verify (rsc.timestamp, + &rsc.gf->fees.open, + reserve_pub, + &rsc.reserve_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE, + NULL); + } + rsc.rh = NULL; + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "reserve open", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_open_transaction, + &rsc)) + { + return mhd_ret; + } + if (NULL == rsc.rh) + { + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + NULL); + } + return reply_reserve_open_success (rc->connection, + &rsc); +} + + +/* end of taler-exchange-httpd_reserves_open.c */ diff --git a/src/exchange/taler-exchange-httpd_reserves_open.h b/src/exchange/taler-exchange-httpd_reserves_open.h new file mode 100644 index 000000000..e28c22c0b --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_open.h @@ -0,0 +1,41 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_open.h + * @brief Handle /reserves/$RESERVE_PUB/open requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H +#define TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a POST "/reserves/$RID/open" request. + * + * @param rc request context + * @param reserve_pub public key of the reserve + * @param root uploaded body from the client + * @return MHD result code + */ +MHD_RESULT +TEH_handler_reserves_open (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + +#endif From 4ea4f03aea4bf78854e19261edba42f17aad1955 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 12:28:40 +0200 Subject: [PATCH 06/17] taler-exchange-httpd_reserves_open.c now builds (but not complete) --- contrib/gana | 2 +- src/exchange/Makefile.am | 1 + src/exchange/taler-exchange-httpd.c | 2 +- .../taler-exchange-httpd_common_deposit.c | 48 ----- .../taler-exchange-httpd_reserves_attest.c | 2 +- .../taler-exchange-httpd_reserves_close.c | 2 +- .../taler-exchange-httpd_reserves_get.c | 2 +- ...taler-exchange-httpd_reserves_get_attest.c | 4 +- .../taler-exchange-httpd_reserves_open.c | 200 ++++++++++++++---- 9 files changed, 164 insertions(+), 99 deletions(-) diff --git a/contrib/gana b/contrib/gana index d402af78f..b26070acf 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit d402af78f6d360841db53baa46dddae13590ec33 +Subproject commit b26070acff35f8ec68a299797d3de05ff5d234ac diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 4d7d6d948..db7aca469 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -163,6 +163,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \ + taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \ taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ taler-exchange-httpd_reserves_status.c taler-exchange-httpd_reserves_status.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 496d3d29f..9349a5a21 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -395,7 +395,7 @@ handle_post_reserves (struct TEH_RequestContext *rc, GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, args[0]); } for (unsigned int i = 0; NULL != h[i].op; i++) diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c index ecb13630c..0c91f0bc9 100644 --- a/src/exchange/taler-exchange-httpd_common_deposit.c +++ b/src/exchange/taler-exchange-httpd_common_deposit.c @@ -265,51 +265,3 @@ TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin) if (! coin->cpi.no_age_commitment) GNUNET_free (coin->age_commitment.keys); /* Only the keys have been allocated */ } - - -#if LEGACY - -if (0 > - TALER_amount_add (&pcc->deposit_total, - &pcc->deposit_total, - &coin->amount_minus_fee)) -{ - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - "total deposit contribution"); -} - - -{ - MHD_RESULT mhd_ret = MHD_NO; - enum GNUNET_DB_QueryStatus qs; - - /* make sure coin is 'known' in database */ - for (unsigned int tries = 0; triescpi, - connection, - &coin->known_coin_id, - &mhd_ret); - /* no transaction => no serialization failures should be possible */ - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - "make_coin_known")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (qs < 0) - return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR; -} -return GNUNET_OK; -} -#endif diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c index 94399f076..2bde9eb2b 100644 --- a/src/exchange/taler-exchange-httpd_reserves_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_attest.c @@ -281,7 +281,7 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, { return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); } mhd_ret = reply_reserve_attest_success (rc->connection, diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c index 286669240..4d44fe6cf 100644 --- a/src/exchange/taler-exchange-httpd_reserves_close.c +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -148,7 +148,7 @@ reserve_close_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index 19fb7df8e..88f7aca9c 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -247,7 +247,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, args[0]); } { diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c index 7d98babc0..741c342eb 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -110,7 +110,7 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, args[0]); } { @@ -132,7 +132,7 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, { return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, args[0]); } return TALER_MHD_REPLY_JSON_PACK ( diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index 9ef736f90..160814b65 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -24,6 +24,7 @@ #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" +#include "taler-exchange-httpd_common_deposit.h" #include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_reserves_open.h" #include "taler-exchange-httpd_responses.h" @@ -47,6 +48,16 @@ struct ReserveOpenContext */ const struct TALER_ReservePublicKeyP *reserve_pub; + /** + * Desired (minimum) expiration time for the reserve. + */ + struct GNUNET_TIME_Timestamp desired_expiration; + + /** + * Actual expiration time for the reserve. + */ + struct GNUNET_TIME_Timestamp reserve_expiration; + /** * Timestamp of the request. */ @@ -57,20 +68,35 @@ struct ReserveOpenContext */ struct TALER_ReserveSignatureP reserve_sig; - /** - * Open of the reserve, set in the callback. - */ - struct TALER_EXCHANGEDB_ReserveOpen *rh; - /** * Global fees applying to the request. */ const struct TEH_GlobalFee *gf; /** - * Current reserve balance. + * Amount to be paid from the reserve. */ - struct TALER_Amount balance; + struct TALER_Amount reserve_payment; + + /** + * Actual cost to open the reserve. + */ + struct TALER_Amount open_cost; + + /** + * Information about payments by coin. + */ + struct TEH_PurseDepositedCoin *payments; + + /** + * Length of the @e payments array. + */ + unsigned int payments_len; + + /** + * Desired minimum purse limit. + */ + uint32_t purse_limit; }; @@ -83,15 +109,32 @@ struct ReserveOpenContext */ static MHD_RESULT reply_reserve_open_success (struct MHD_Connection *connection, - const struct ReserveOpenContext *rhc) + const struct ReserveOpenContext *rsc) { - const struct TALER_EXCHANGEDB_ReserveOpen *rh = rhc->rh; - return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - TALER_JSON_pack_amount ("balance", - &rhc->balance)); + GNUNET_JSON_pack_timestamp ("reserve_expiration", + rsc->reserve_expiration), + TALER_JSON_pack_amount ("open_cost", + &rsc->open_cost)); +} + + +/** + * Cleans up information in @a rsc, but does not + * free @a rsc itself (allocated on the stack!). + * + * @param[in] rsc struct with information to clean up + */ +static void +cleanup_rsc (struct ReserveOpenContext *rsc) +{ + for (unsigned int i = 0; ipayments_len; i++) + { + TEH_common_purse_deposit_free_coin (&rsc->payments[i]); + } + GNUNET_free (rsc->payments); } @@ -118,6 +161,8 @@ reserve_open_transaction (void *cls, struct ReserveOpenContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; + (void) rsc; +#if 0 if (! TALER_amount_is_zero (&rsc->gf->fees.open)) { bool balance_ok = false; @@ -161,14 +206,29 @@ reserve_open_transaction (void *cls, rsc->reserve_pub, &rsc->balance, &rsc->rh); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) +#endif + qs = GNUNET_DB_STATUS_HARD_ERROR; + switch (qs) { + case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "get_reserve_open"); + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SOFT_ERROR: + return qs; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; } return qs; } @@ -180,15 +240,23 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, const json_t *root) { struct ReserveOpenContext rsc; - MHD_RESULT mhd_ret; + json_t *payments; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("request_timestamp", &rsc.timestamp), + GNUNET_JSON_spec_timestamp ("reserve_expiration", + &rsc.desired_expiration), GNUNET_JSON_spec_fixed_auto ("reserve_sig", &rsc.reserve_sig), + GNUNET_JSON_spec_uint32 ("purse_limit", + &rsc.purse_limit), + GNUNET_JSON_spec_json ("payments", + &payments), + TALER_JSON_spec_amount ("reserve_payment", + TEH_currency, + &rsc.reserve_payment), GNUNET_JSON_spec_end () }; - struct GNUNET_TIME_Timestamp now; rsc.reserve_pub = reserve_pub; { @@ -208,17 +276,50 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, return MHD_YES; /* failure */ } } - now = GNUNET_TIME_timestamp_get (); - if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, - rsc.timestamp.abs_time, - TIMESTAMP_TOLERANCE)) + { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, - NULL); + struct GNUNET_TIME_Timestamp now; + + now = GNUNET_TIME_timestamp_get (); + if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, + rsc.timestamp.abs_time, + TIMESTAMP_TOLERANCE)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, + NULL); + } } + + rsc.payments_len = json_array_size (payments); + rsc.payments = GNUNET_new_array (rsc.payments_len, + struct TEH_PurseDepositedCoin); + for (unsigned int i = 0; iconnection, + coin, + json_array_get (payments, + i)); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + cleanup_rsc (&rsc); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + cleanup_rsc (&rsc); + return MHD_YES; /* failure */ + } + } + { struct TEH_KeyStateHandle *keys; @@ -227,6 +328,7 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, { GNUNET_break (0); GNUNET_JSON_parse_free (spec); + cleanup_rsc (&rsc); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, @@ -238,43 +340,53 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, if (NULL == rsc.gf) { GNUNET_break (0); + cleanup_rsc (&rsc); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, NULL); } + if (GNUNET_OK != - TALER_wallet_reserve_open_verify (rsc.timestamp, - &rsc.gf->fees.open, + TALER_wallet_reserve_open_verify (&rsc.reserve_payment, + rsc.timestamp, + rsc.desired_expiration, + rsc.purse_limit, reserve_pub, &rsc.reserve_sig)) { GNUNET_break_op (0); + cleanup_rsc (&rsc); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE, NULL); } - rsc.rh = NULL; - if (GNUNET_OK != - TEH_DB_run_transaction (rc->connection, - "reserve open", - TEH_MT_REQUEST_OTHER, - &mhd_ret, - &reserve_open_transaction, - &rsc)) + { + MHD_RESULT mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "reserve open", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_open_transaction, + &rsc)) + { + cleanup_rsc (&rsc); + return mhd_ret; + } + } + + { + MHD_RESULT mhd_ret; + + mhd_ret = reply_reserve_open_success (rc->connection, + &rsc); + cleanup_rsc (&rsc); return mhd_ret; } - if (NULL == rsc.rh) - { - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN, - NULL); - } - return reply_reserve_open_success (rc->connection, - &rsc); } From bd3741c1c188680245d66190bb5c8fbbc886014a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 12:41:37 +0200 Subject: [PATCH 07/17] -taler-exchange-httpd_reserves_close.c now builds (but not complete) --- contrib/gana | 2 +- src/exchange/Makefile.am | 1 + .../taler-exchange-httpd_reserves_close.c | 63 +++++++++---------- .../taler-exchange-httpd_reserves_open.c | 50 ++------------- 4 files changed, 38 insertions(+), 78 deletions(-) diff --git a/contrib/gana b/contrib/gana index b26070acf..57d96e8e1 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit b26070acff35f8ec68a299797d3de05ff5d234ac +Subproject commit 57d96e8e123df90c804a821874fc6cb88671ab75 diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index db7aca469..aea5dd813 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -161,6 +161,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \ taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ + taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \ taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \ diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c index 4d44fe6cf..40fba25f9 100644 --- a/src/exchange/taler-exchange-httpd_reserves_close.c +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -26,7 +26,7 @@ #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" -#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_wire.h" #include "taler-exchange-httpd_reserves_close.h" #include "taler-exchange-httpd_responses.h" @@ -60,9 +60,9 @@ struct ReserveCloseContext struct TALER_ReserveSignatureP reserve_sig; /** - * Global fees applying to the request. + * Wire fees applying to the request. */ - const struct TEH_GlobalFee *gf; + const struct TALER_WireFeeSet *wf; /** * Amount that will be wired (after closing fees). @@ -93,8 +93,6 @@ static MHD_RESULT reply_reserve_close_success (struct MHD_Connection *connection, const struct ReserveCloseContext *rhc) { - const struct TALER_EXCHANGEDB_ReserveClose *rh = rhc->rh; - return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, @@ -128,10 +126,15 @@ reserve_close_transaction (void *cls, struct TALER_Amount balance; char *payto_uri; +#if FIXME qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, rcc->reserve_pub, &balance, &payto_uri); +#else + qs = GNUNET_DB_STATUS_HARD_ERROR; + payto_uri = NULL; +#endif switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -146,11 +149,13 @@ reserve_close_transaction (void *cls, return qs; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: *mhd_ret - = TALER_MHD_reply_with_error (rc->connection, + = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; } if ( (NULL == rcc->payto_uri) && @@ -159,7 +164,7 @@ reserve_close_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, - TALER_EC_RESERVE_CLOSE_NO_TARGET_ACCOUNT, + TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } @@ -177,7 +182,7 @@ reserve_close_transaction (void *cls, and KYC not yet done (check KYC triggers!), fail with 451 kyc required! */ *mhd_ret - = TEH_RESPONSE_reply_kyc_required (rcc->connection, + = TEH_RESPONSE_reply_kyc_required (connection, &kyc_payto, &kyc); return GNUNET_DB_STATUS_HARD_ERROR; @@ -189,26 +194,30 @@ reserve_close_transaction (void *cls, if (0 > TALER_amount_subtract (&rcc->wire_amount, &balance, - &rcc->gf->fees.close)) + &rcc->wf->closing)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client attempted to close reserve with insufficient balance.\n"); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TEH_currency, &rcc->wire_amount)); - *mhd_ret = reply_reserve_close_success (rc->connection, - &rcc); + *mhd_ret = reply_reserve_close_success (connection, + rcc); return GNUNET_DB_STATUS_HARD_ERROR; } { +#if FIXME qs = TEH_plugin->insert_close_request (TEH_plugin->cls, rcc->reserve_pub, payto_uri, &rcc->reserve_sig, rcc->timestamp, - &rcc->gf->fees.close, + &rcc->wf->closing, &rcc->wire_amount); +#else + qs = GNUNET_DB_STATUS_HARD_ERROR; +#endif GNUNET_free (payto_uri); if (GNUNET_DB_STATUS_HARD_ERROR == qs) { @@ -243,7 +252,7 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("request_timestamp", &rcc.timestamp), - GNUNET_JSON_spec_allow_null ( + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("payto_uri", &rcc.payto_uri), NULL), @@ -286,32 +295,20 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc, } } - { - struct TEH_KeyStateHandle *keys; - - keys = TEH_keys_get_state (); - if (NULL == keys) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); - } - rcc.gf = TEH_keys_global_fee_by_time (keys, - rcc.timestamp); - } - if (NULL == rcc.gf) + // FIXME: can only do this later, as we may get the payto://-URI + // with the method from the database! + rcc.wf = TEH_wire_fees_by_time (rcc.timestamp, + "FIXME-method"); + if (NULL == rcc.wf) { GNUNET_break (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, - NULL); + TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, + "FIXME-method"); } if (NULL != rcc.payto_uri) - TALER_payto_hash (&rcc.payto_uri, + TALER_payto_hash (rcc.payto_uri, &rcc.h_payto); if (GNUNET_OK != TALER_wallet_reserve_close_verify (rcc.timestamp, diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index 160814b65..2bc450ee2 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -163,51 +163,13 @@ reserve_open_transaction (void *cls, (void) rsc; #if 0 - if (! TALER_amount_is_zero (&rsc->gf->fees.open)) - { - bool balance_ok = false; - bool idempotent = true; - - qs = TEH_plugin->insert_open_request (TEH_plugin->cls, - rsc->reserve_pub, - &rsc->reserve_sig, - rsc->timestamp, - &rsc->gf->fees.open, - &balance_ok, - &idempotent); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret - = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_open"); - } - if (qs <= 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (! balance_ok) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_WITHDRAW_OPEN_ERROR_INSUFFICIENT_FUNDS, - NULL); - } - if (idempotent) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Idempotent /reserves/open request observed. Is caching working?\n"); - } - } - qs = TEH_plugin->get_reserve_open (TEH_plugin->cls, - rsc->reserve_pub, - &rsc->balance, - &rsc->rh); -#endif + // FIXME: implement! + qs = TEH_plugin->do_reserve_open (TEH_plugin->cls, + rsc->reserve_pub, + ...); +#else qs = GNUNET_DB_STATUS_HARD_ERROR; +#endif switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: From 2d9270a01d48aa1408e049425ee2398b4cbd1011 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 13:22:57 +0200 Subject: [PATCH 08/17] -get taler-exchange-httpd_reserves_get_attest.c to build (but not finished) --- src/exchange/Makefile.am | 1 + ...taler-exchange-httpd_reserves_get_attest.c | 8 +++- ...taler-exchange-httpd_reserves_get_attest.h | 44 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_reserves_get_attest.h diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index aea5dd813..9a4c107c9 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -163,6 +163,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ + taler-exchange-httpd_reserves_get_attest.c taler-exchange-httpd_reserves_get_attest.h \ taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \ taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \ taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c index 741c342eb..8fd346c28 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -75,9 +75,13 @@ reserve_attest_transaction (void *cls, struct ReserveAttestContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; +#if FIXME qs = TEH_plugin->get_reserve_attributes (TEH_plugin->cls, &rsc->reserve_pub, &rsc->attributes); +#else + qs = GNUNET_DB_STATUS_HARD_ERROR; +#endif if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); @@ -138,8 +142,8 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, return TALER_MHD_REPLY_JSON_PACK ( rc->connection, MHD_HTTP_OK, - TALER_JSON_pack_object_steal ("attributes", - &rsc.attributes)); + GNUNET_JSON_pack_object_steal ("attributes", + rsc.attributes)); } diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.h b/src/exchange/taler-exchange-httpd_reserves_get_attest.h new file mode 100644 index 000000000..8b5e3aba3 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.h @@ -0,0 +1,44 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_reserves_get_attest.h + * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H +#define TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a GET "/reserves/$RID/attest" request. Parses the + * given "reserve_pub" in @a args (which should contain the + * EdDSA public key of a reserve) and then responds with the + * available attestations for the reserve. + * + * @param rc request context + * @param args array of additional options (length: 1, just the reserve_pub) + * @return MHD result code + */ +MHD_RESULT +TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, + const char *const args[1]); + +#endif From de657800a854a031e61f5bcc7d1c168150d1c626 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 13:34:51 +0200 Subject: [PATCH 09/17] -get taler-exchange-httpd_reserves_attest.c to build (but not finished) --- src/exchange/Makefile.am | 1 + .../taler-exchange-httpd_reserves_attest.c | 157 +++++++----------- 2 files changed, 59 insertions(+), 99 deletions(-) diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 9a4c107c9..b2365eecb 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -161,6 +161,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \ taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ + taler-exchange-httpd_reserves_attest.c taler-exchange-httpd_reserves_attest.h \ taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ taler-exchange-httpd_reserves_get_attest.c taler-exchange-httpd_reserves_get_attest.h \ diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c index 2bde9eb2b..6a030dbd3 100644 --- a/src/exchange/taler-exchange-httpd_reserves_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_attest.c @@ -14,8 +14,8 @@ TALER; see the file COPYING. If not, see */ /** - * @file taler-exchange-httpd_reserves_close.c - * @brief Handle /reserves/$RESERVE_PUB/close requests + * @file taler-exchange-httpd_reserves_attest.c + * @brief Handle /reserves/$RESERVE_PUB/attest requests * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff @@ -54,25 +54,26 @@ struct ReserveAttestContext */ struct GNUNET_TIME_Timestamp timestamp; + /** + * Expiration time for the attestation. + */ + struct GNUNET_TIME_Timestamp etime; + + /** + * List of requested details. + */ + json_t *details; + /** * Client signature approving the request. */ struct TALER_ReserveSignatureP reserve_sig; /** - * Attest of the reserve, set in the callback. + * Attributes we are affirming. */ - struct TALER_EXCHANGEDB_ReserveAttest *rh; + json_t *json_attest; - /** - * Global fees applying to the request. - */ - const struct TEH_GlobalFee *gf; - - /** - * Current reserve balance. - */ - struct TALER_Amount balance; }; @@ -87,22 +88,44 @@ static MHD_RESULT reply_reserve_attest_success (struct MHD_Connection *connection, const struct ReserveAttestContext *rhc) { - const struct TALER_EXCHANGEDB_ReserveAttest *rh = rhc->rh; - json_t *json_attest; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + enum TALER_ErrorCode ec; + struct GNUNET_TIME_Timestamp now; - json_attest = TEH_RESPONSE_compile_reserve_attest (rh); - if (NULL == json_attest) + if (NULL == rhc->json_attest) + { + GNUNET_break (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, NULL); + } + now = GNUNET_TIME_timestamp_get (); + ec = TALER_exchange_online_reserve_attest_details_sign ( + &TEH_keys_exchange_sign_, + now, + rhc->etime, + rhc->reserve_pub, + rhc->json_attest, + &exchange_pub, + &exchange_sig); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + } return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - TALER_JSON_pack_amount ("balance", - &rhc->balance), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &exchange_pub), GNUNET_JSON_pack_array_steal ("attest", - json_attest)); + rhc->json_attest)); } @@ -129,49 +152,15 @@ reserve_attest_transaction (void *cls, struct ReserveAttestContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; - if (! TALER_amount_is_zero (&rsc->gf->fees.attest)) - { - bool balance_ok = false; - bool idempotent = true; - - qs = TEH_plugin->insert_attest_request (TEH_plugin->cls, - rsc->reserve_pub, - &rsc->reserve_sig, - rsc->timestamp, - &rsc->gf->fees.attest, - &balance_ok, - &idempotent); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret - = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_attest"); - } - if (qs <= 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } - if (! balance_ok) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_WITHDRAW_ATTEST_ERROR_INSUFFICIENT_FUNDS, - NULL); - } - if (idempotent) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Idempotent /reserves/attest request observed. Is caching working?\n"); - } - } +#if FIXME qs = TEH_plugin->get_reserve_attest (TEH_plugin->cls, rsc->reserve_pub, - &rsc->balance, - &rsc->rh); + &rsc->json_attest, + &etime); +#else + qs = GNUNET_DB_STATUS_HARD_ERROR; + (void) rsc; +#endif if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); @@ -181,6 +170,7 @@ reserve_attest_transaction (void *cls, TALER_EC_GENERIC_DB_FETCH_FAILED, "get_reserve_attest"); } + // FIXME: filter json_attest by requested attributes! return qs; } @@ -195,6 +185,8 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("request_timestamp", &rsc.timestamp), + GNUNET_JSON_spec_json ("details", + &rsc.details), GNUNET_JSON_spec_fixed_auto ("reserve_sig", &rsc.reserve_sig), GNUNET_JSON_spec_end () @@ -230,35 +222,12 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, NULL); } - { - struct TEH_KeyStateHandle *keys; - keys = TEH_keys_get_state (); - if (NULL == keys) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); - } - rsc.gf = TEH_keys_global_fee_by_time (keys, - rsc.timestamp); - } - if (NULL == rsc.gf) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, - NULL); - } if (GNUNET_OK != - TALER_wallet_reserve_attest_verify (rsc.timestamp, - &rsc.gf->fees.attest, - reserve_pub, - &rsc.reserve_sig)) + TALER_wallet_reserve_attest_request_verify (rsc.timestamp, + rsc.details, + reserve_pub, + &rsc.reserve_sig)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, @@ -266,10 +235,9 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, NULL); } - rsc.rh = NULL; if (GNUNET_OK != TEH_DB_run_transaction (rc->connection, - "get reserve attest", + "post reserve attest", TEH_MT_REQUEST_OTHER, &mhd_ret, &reserve_attest_transaction, @@ -277,17 +245,8 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, { return mhd_ret; } - if (NULL == rsc.rh) - { - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - NULL); - } mhd_ret = reply_reserve_attest_success (rc->connection, &rsc); - TEH_plugin->free_reserve_attest (TEH_plugin->cls, - rsc.rh); return mhd_ret; } From 4a36ed7fbfcaa220d1b2605851b38fc1a386e7d3 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 22:47:28 +0200 Subject: [PATCH 10/17] complete taler-exchange-httpd_reserves_open.c logic (first pass, still without DB logic or tests) --- .../taler-exchange-httpd_reserves_open.c | 96 +++++++++++++++++-- src/include/taler_crypto_lib.h | 12 +-- src/include/taler_exchangedb_plugin.h | 49 ++++++++++ src/lib/exchange_api_reserves_open.c | 15 ++- src/util/wallet_signatures.c | 21 ++-- 5 files changed, 152 insertions(+), 41 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index 2bc450ee2..c9f5e4019 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -83,6 +83,11 @@ struct ReserveOpenContext */ struct TALER_Amount open_cost; + /** + * Total amount that was deposited. + */ + struct TALER_Amount total; + /** * Information about payments by coin. */ @@ -111,9 +116,16 @@ static MHD_RESULT reply_reserve_open_success (struct MHD_Connection *connection, const struct ReserveOpenContext *rsc) { + unsigned int status; + + status = MHD_HTTP_OK; + if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration, + <, + rsc->desired_expiration)) + status = MHD_HTTP_PAYMENT_REQUIRED; return TALER_MHD_REPLY_JSON_PACK ( connection, - MHD_HTTP_OK, + status, GNUNET_JSON_pack_timestamp ("reserve_expiration", rsc->reserve_expiration), TALER_JSON_pack_amount ("open_cost", @@ -150,7 +162,7 @@ cleanup_rsc (struct ReserveOpenContext *rsc) * @param cls a `struct ReserveOpenContext *` * @param connection MHD request which triggered the transaction * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!); unused + * if transaction failed (!) * @return transaction status */ static enum GNUNET_DB_QueryStatus @@ -161,15 +173,63 @@ reserve_open_transaction (void *cls, struct ReserveOpenContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; - (void) rsc; -#if 0 - // FIXME: implement! + for (unsigned int i = 0; ipayments_len; i++) + { + struct TEH_PurseDepositedCoin *coin = &rsc->payments[i]; + bool insufficient_funds = true; + + qs = TEH_make_coin_known (&coin->cpi, + connection, + &coin->known_coin_id, + mhd_ret); + if (qs < 0) + return qs; + qs = TEH_plugin->insert_reserve_open_deposit ( + TEH_plugin->cls, + &coin->cpi, + &coin->coin_sig, + coin->known_coin_id, + &coin->amount, + &rsc->reserve_sig, + &insufficient_funds); + /* 0 == qs is fine, then the coin was already + spent for this very operation as identified + by reserve_sig! */ + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_reserve_open_deposit"); + return qs; + } + if (insufficient_funds) + { + *mhd_ret + = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, + &coin->cpi.denom_pub_hash, + &coin->cpi.coin_pub); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + qs = TEH_plugin->do_reserve_open (TEH_plugin->cls, + /* inputs */ rsc->reserve_pub, - ...); -#else - qs = GNUNET_DB_STATUS_HARD_ERROR; -#endif + &rsc->total, + rsc->purse_limit, + &rsc->reserve_sig, + rsc->desired_expiration, + rsc->timestamp, + &rsc->gf->fees.account, + /* outputs */ + &rsc->open_cost, + &rsc->reserve_expiration); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -178,7 +238,7 @@ reserve_open_transaction (void *cls, = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_open"); + "do_reserve_open"); return GNUNET_DB_STATUS_HARD_ERROR; case GNUNET_DB_STATUS_SOFT_ERROR: return qs; @@ -258,6 +318,7 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, rsc.payments_len = json_array_size (payments); rsc.payments = GNUNET_new_array (rsc.payments_len, struct TEH_PurseDepositedCoin); + rsc.total = rsc.reserve_payment; for (unsigned int i = 0; iamount_minus_fee and + thereby charge the deposit fee even when paying the reserve-open fee. + To be decided... */ + if (0 > + TALER_amount_add (&rsc.total, + &rsc.total, + &coin->amount)) + { + GNUNET_break (0); + cleanup_rsc (&rsc); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + NULL); + } } { diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 09e386cd4..a861563b2 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -3107,16 +3107,14 @@ TALER_wallet_reserve_open_verify ( * Sign to deposit coin to pay for keeping a reserve open. * * @param coin_contribution how much the coin should contribute - * @param reserve_pub public key of the reserve - * @param request_timestamp time of the open request + * @param reserve_sig signature over the reserve open operation * @param coin_priv private key of the coin * @param[out] coin_sig signature by the coin */ void TALER_wallet_reserve_open_deposit_sign ( const struct TALER_Amount *coin_contribution, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPrivateKeyP *coin_priv, struct TALER_CoinSpendSignatureP *coin_sig); @@ -3125,8 +3123,7 @@ TALER_wallet_reserve_open_deposit_sign ( * Verify signature that deposits coin to pay for keeping a reserve open. * * @param coin_contribution how much the coin should contribute - * @param reserve_pub public key of the reserve - * @param request_timestamp time of the open request + * @param reserve_sig signature over the reserve open operation * @param coin_pub public key of the coin * @param coin_sig signature by the coin * @return #GNUNET_OK if the signature is valid @@ -3134,8 +3131,7 @@ TALER_wallet_reserve_open_deposit_sign ( enum GNUNET_GenericReturnValue TALER_wallet_reserve_open_deposit_verify ( const struct TALER_Amount *coin_contribution, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 261ffb184..e6c38ad9b 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4043,6 +4043,55 @@ struct TALER_EXCHANGEDB_Plugin void *rec_cls); + /** + * Insert reserve open coin deposit data into database. + * Subtracts the @a coin_total from the coin's balance. + * + * @param cls closure + * @param cpi public information about the coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT + * @param known_coin_id ID of the coin in the known_coins table + * @param coin_total amount to be spent of the coin (including deposit fee) + * @param reserve_sig signature by the reserve affirming the open operation + * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false + * @return transaction status code, 0 if operation is already in the DB + */ + enum GNUNET_DB_QueryStatus + (*insert_reserve_open_deposit)( + void *cls, + const struct TALER_CoinPublicInfo *cpi, + const struct TALER_CoinSpendSignatureP *coin_sig, + uint64_t known_coin_id, + const struct TALER_Amount *coin_total, + const struct TALER_ReserveSignatureP *reserve_sig, + bool *insufficient_funds); + + + /** + * Insert reserve close operation into database. + * + * @param cls closure + * @param reserve_pub which reserve is this about? + * @param execution_date when did we perform the transfer? + * @param receiver_account to which account do we transfer, in payto://-format + * @param wtid identifier for the wire transfer + * @param amount_with_fee amount we charged to the reserve + * @param closing_fee how high is the closing fee + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*do_reserve_open)(void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *total_paid, + uint32_t min_purse_limit, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Timestamp desired_expiration, + struct GNUNET_TIME_Timestamp now, + const struct TALER_Amount *open_fee, + struct TALER_Amount *open_cost, + const struct GNUNET_TIME_Timestamp *final_expiration); + + /** * Insert reserve close operation into database. * diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c index eb63f8fe9..08d267fb7 100644 --- a/src/lib/exchange_api_reserves_open.c +++ b/src/lib/exchange_api_reserves_open.c @@ -393,6 +393,12 @@ TALER_EXCHANGE_reserves_open ( GNUNET_free (roh); return NULL; } + TALER_wallet_reserve_open_sign (reserve_contribution, + roh->ts, + expiration_time, + min_purses, + reserve_priv, + &roh->reserve_sig); cpa = json_array (); GNUNET_assert (NULL != cpa); for (unsigned int i = 0; iamount, - &roh->reserve_pub, - roh->ts, + &roh->reserve_sig, &pd->coin_priv, &coin_sig); GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, @@ -437,12 +442,6 @@ TALER_EXCHANGE_reserves_open ( json_array_append_new (cpa, cp)); } - TALER_wallet_reserve_open_sign (reserve_contribution, - roh->ts, - expiration_time, - min_purses, - reserve_priv, - &roh->reserve_sig); { json_t *open_obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_timestamp ("request_timestamp", diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 4e4932a2b..c57506bd8 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -1342,14 +1342,9 @@ struct TALER_ReserveOpenDepositPS struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** - * When was the request created. + * Which reserve's opening signature should be paid for? */ - struct GNUNET_TIME_TimestampNBO request_timestamp; - - /** - * Which reserve's opening should be paid for? - */ - struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_ReserveSignatureP reserve_sig; /** * Specifies how much of the coin's value should be spent on opening this @@ -1364,16 +1359,14 @@ GNUNET_NETWORK_STRUCT_END void TALER_wallet_reserve_open_deposit_sign ( const struct TALER_Amount *coin_contribution, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPrivateKeyP *coin_priv, struct TALER_CoinSpendSignatureP *coin_sig) { struct TALER_ReserveOpenDepositPS rod = { .purpose.size = htonl (sizeof (rod)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), - .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp), - .reserve_pub = *reserve_pub + .reserve_sig = *reserve_sig }; TALER_amount_hton (&rod.coin_contribution, @@ -1388,16 +1381,14 @@ TALER_wallet_reserve_open_deposit_sign ( enum GNUNET_GenericReturnValue TALER_wallet_reserve_open_deposit_verify ( const struct TALER_Amount *coin_contribution, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig) { struct TALER_ReserveOpenDepositPS rod = { .purpose.size = htonl (sizeof (rod)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), - .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp), - .reserve_pub = *reserve_pub + .reserve_sig = *reserve_sig }; TALER_amount_hton (&rod.coin_contribution, From 1ce70b1dabb390d04b3b886816e49237288b9b31 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 23:19:48 +0200 Subject: [PATCH 11/17] complete taler-exchange-httpd_reserves_close.c logic (first pass, still without DB logic or tests) --- .../taler-exchange-httpd_reserves_close.c | 216 ++++++++++++------ src/include/taler_exchangedb_plugin.h | 45 ++++ src/include/taler_kyclogic_lib.h | 7 +- src/kyclogic/kyclogic_api.c | 3 + 4 files changed, 202 insertions(+), 69 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c index 40fba25f9..6d998bb93 100644 --- a/src/exchange/taler-exchange-httpd_reserves_close.c +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -23,6 +23,7 @@ #include "platform.h" #include #include +#include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" @@ -59,26 +60,40 @@ struct ReserveCloseContext */ struct TALER_ReserveSignatureP reserve_sig; - /** - * Wire fees applying to the request. - */ - const struct TALER_WireFeeSet *wf; - /** * Amount that will be wired (after closing fees). */ struct TALER_Amount wire_amount; + /** + * Current balance of the reserve. + */ + struct TALER_Amount balance; + /** * Where to wire the funds, may be NULL. */ const char *payto_uri; /** - * Hash of the @e payto_uri. + * Hash of the @e payto_uri, if given (otherwise zero). */ struct TALER_PaytoHashP h_payto; + /** + * KYC status for the request. + */ + struct TALER_EXCHANGEDB_KycStatus kyc; + + /** + * Hash of the payto-URI that was used for the KYC decision. + */ + struct TALER_PaytoHashP kyc_payto; + + /** + * Query status from the amount_it() helper function. + */ + enum GNUNET_DB_QueryStatus qs; }; @@ -101,6 +116,46 @@ reply_reserve_close_success (struct MHD_Connection *connection, } +/** + * Function called to iterate over KYC-relevant + * transaction amounts for a particular time range. + * Called within a database transaction, so must + * not start a new one. + * + * @param cls closure, identifies the event type and + * account to iterate over events for + * @param limit maximum time-range for which events + * should be fetched (timestamp in the past) + * @param cb function to call on each event found, + * events must be returned in reverse chronological + * order + * @param cb_cls closure for @a cb + */ +static void +amount_it (void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) +{ + struct ReserveCloseContext *rcc = cls; + enum GNUNET_GenericReturnValue ret; + + ret = cb (cb_cls, + &rcc->balance, + GNUNET_TIME_absolute_get ()); + GNUNET_break (GNUNET_SYSERR != ret); + if (GNUNET_OK != ret) + return; + rcc->qs + = TEH_plugin->iterate_reserve_close_info ( + TEH_plugin->cls, + &rcc->kyc_payto, + limit, + cb, + cb_cls); +} + + /** * Function implementing /reserves/$RID/close transaction. Given the public * key of a reserve, return the associated transaction close. Runs the @@ -124,17 +179,14 @@ reserve_close_transaction (void *cls, struct ReserveCloseContext *rcc = cls; enum GNUNET_DB_QueryStatus qs; struct TALER_Amount balance; - char *payto_uri; + char *payto_uri = NULL; + const struct TALER_WireFeeSet *wf; -#if FIXME - qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls, - rcc->reserve_pub, - &balance, - &payto_uri); -#else - qs = GNUNET_DB_STATUS_HARD_ERROR; - payto_uri = NULL; -#endif + qs = TEH_plugin->select_reserve_close_info ( + TEH_plugin->cls, + rcc->reserve_pub, + &balance, + &payto_uri); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -143,7 +195,7 @@ reserve_close_transaction (void *cls, = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_balance"); + "select_reserve_close_info"); return qs; case GNUNET_DB_STATUS_SOFT_ERROR: return qs; @@ -174,27 +226,66 @@ reserve_close_transaction (void *cls, (0 != strcmp (payto_uri, rcc->payto_uri)) ) ) { - struct TALER_EXCHANGEDB_KycStatus kyc; - struct TALER_PaytoHashP kyc_payto; + const char *kyc_needed; - /* FIXME: also fetch KYC status from reserve - in query above, and if payto_uri specified - and KYC not yet done (check KYC triggers!), - fail with 451 kyc required! */ - *mhd_ret - = TEH_RESPONSE_reply_kyc_required (connection, - &kyc_payto, - &kyc); - return GNUNET_DB_STATUS_HARD_ERROR; + TALER_payto_hash (rcc->payto_uri, + &rcc->kyc_payto); + rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + kyc_needed + = TALER_KYCLOGIC_kyc_test_required ( + TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE, + &rcc->kyc_payto, + TEH_plugin->select_satisfied_kyc_processes, + TEH_plugin->cls, + &amount_it, + rcc); + if (rcc->qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs) + return rcc->qs; + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "iterate_reserve_close_info"); + return qs; + } + rcc->kyc.ok = false; + return TEH_plugin->insert_kyc_requirement_for_account ( + TEH_plugin->cls, + kyc_needed, + &rcc->kyc_payto, + &rcc->kyc.requirement_row); } + rcc->kyc.ok = true; if (NULL == rcc->payto_uri) rcc->payto_uri = payto_uri; + { + char *method; + + method = TALER_payto_get_method (rcc->payto_uri); + wf = TEH_wire_fees_by_time (rcc->timestamp, + method); + if (NULL == wf) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, + method); + GNUNET_free (method); + return GNUNET_DB_STATUS_HARD_ERROR; + } + GNUNET_free (method); + } + if (0 > TALER_amount_subtract (&rcc->wire_amount, &balance, - &rcc->wf->closing)) + &wf->closing)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client attempted to close reserve with insufficient balance.\n"); @@ -206,34 +297,29 @@ reserve_close_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } + + qs = TEH_plugin->insert_close_request (TEH_plugin->cls, + rcc->reserve_pub, + payto_uri, + &rcc->reserve_sig, + rcc->timestamp, + &wf->closing, + &rcc->wire_amount); + GNUNET_free (payto_uri); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) { -#if FIXME - qs = TEH_plugin->insert_close_request (TEH_plugin->cls, - rcc->reserve_pub, - payto_uri, - &rcc->reserve_sig, - rcc->timestamp, - &rcc->wf->closing, - &rcc->wire_amount); -#else - qs = GNUNET_DB_STATUS_HARD_ERROR; -#endif - GNUNET_free (payto_uri); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret - = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "insert_close_request"); - return qs; - } - if (qs <= 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - return qs; - } + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "insert_close_request"); + return qs; + } + if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; } return qs; } @@ -295,18 +381,6 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc, } } - // FIXME: can only do this later, as we may get the payto://-URI - // with the method from the database! - rcc.wf = TEH_wire_fees_by_time (rcc.timestamp, - "FIXME-method"); - if (NULL == rcc.wf) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, - "FIXME-method"); - } if (NULL != rcc.payto_uri) TALER_payto_hash (rcc.payto_uri, &rcc.h_payto); @@ -322,6 +396,7 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc, TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE, NULL); } + if (GNUNET_OK != TEH_DB_run_transaction (rc->connection, "reserve close", @@ -332,6 +407,11 @@ TEH_handler_reserves_close (struct TEH_RequestContext *rc, { return mhd_ret; } + if (! rcc.kyc.ok) + return TEH_RESPONSE_reply_kyc_required (rc->connection, + &rcc.kyc_payto, + &rcc.kyc); + return reply_reserve_close_success (rc->connection, &rcc); } diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index e6c38ad9b..609265f1e 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4092,6 +4092,47 @@ struct TALER_EXCHANGEDB_Plugin const struct GNUNET_TIME_Timestamp *final_expiration); + /** + * Select information needed to see if we can close + * a reserve. + * + * @param cls closure + * @param reserve_pub which reserve is this about? + * @param[out] balance current reserve balance + * @param[out] payto_uri set to URL of account that + * originally funded the reserve; + * could be set to NULL if not known + * @return transaction status code, 0 if reserve unknown + */ + enum GNUNET_DB_QueryStatus + (*select_reserve_close_info)( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, + char **payto_uri); + + + /** + * Select information needed for KYC checks on reserve close: historic + * reserve closures going to the same account. + * + * @param cls closure + * @param h_payto which target account is this about? + * @param h_payto account identifier + * @param time_limit oldest transaction that could be relevant + * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK). + * @param kac_cls closure for @a kac + * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error + */ + enum GNUNET_DB_QueryStatus + (*iterate_reserve_close_info)( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls); + + /** * Insert reserve close operation into database. * @@ -5537,16 +5578,20 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls the @e cls of this struct with the plugin-specific state * @param reserve_pub public key of the account to close + * @param payto_uri where to wire the funds * @param reserve_sig signature affiming that the account is to be closed * @param request_timestamp timestamp of the close request + * @param closing_fee closing fee to charge * @param[out] final_balance set to the final balance in the account that will be wired back to the origin account * @return transaction status code */ enum GNUNET_DB_QueryStatus (*insert_close_request)(void *cls, const struct TALER_ReservePublicKeyP *reserve_pub, + const char *payto_uri, const struct TALER_ReserveSignatureP *reserve_sig, struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_Amount *closing_fee, struct TALER_Amount *final_balance); diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h index 494f7787d..2ff652f9e 100644 --- a/src/include/taler_kyclogic_lib.h +++ b/src/include/taler_kyclogic_lib.h @@ -68,7 +68,12 @@ enum TALER_KYCLOGIC_KycTriggerEvent /** * Wallet balance exceeds threshold. */ - TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3 + TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3, + + /** + * Reserve is being closed by force. + */ + TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 4 }; diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 96bf16aa7..b04c24194 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -183,6 +183,7 @@ TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s, { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT }, { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE }, { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE }, + { "close", TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE }, { NULL, 0 } }; @@ -213,6 +214,8 @@ TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger) return "merge"; case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE: return "balance"; + case TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE: + return "close"; } GNUNET_break (0); return NULL; From 3bca75d6cfb4c28e9800198d606ae030930e126b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 2 Oct 2022 23:57:09 +0200 Subject: [PATCH 12/17] complete taler-exchange-httpd_reserves_get_attest.c logic (first pass, still without DB logic or tests) --- ...taler-exchange-httpd_reserves_get_attest.c | 119 ++++++++++++++++-- src/include/taler_exchangedb_plugin.h | 37 ++++++ src/include/taler_kyclogic_lib.h | 21 ++++ src/kyclogic/kyclogic_api.c | 14 +++ 4 files changed, 178 insertions(+), 13 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c index 8fd346c28..bd574acd2 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -21,6 +21,7 @@ #include "platform.h" #include #include +#include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" @@ -39,11 +40,21 @@ struct ReserveAttestContext */ struct TALER_ReservePublicKeyP reserve_pub; + /** + * Hash of the payto URI of this reserve. + */ + struct TALER_PaytoHashP h_payto; + /** * Available attributes. */ json_t *attributes; + /** + * Error code encountered in interaction with KYC provider. + */ + enum TALER_ErrorCode ec; + /** * Set to true if we did not find the reserve. */ @@ -51,6 +62,62 @@ struct ReserveAttestContext }; +/** + * Function called with information about all applicable + * legitimization processes for the given user. + * + * @param cls our `struct ReserveAttestContext *` + * @param provider_section KYC provider configuration section + * @param provider_user_id UID at a provider (can be NULL) + * @param legi_id legitimization process ID (can be NULL) + */ +static void +kyc_process_cb (void *cls, + const char *provider_section, + const char *provider_user_id, + const char *legi_id) +{ + struct ReserveAttestContext *rsc = cls; + struct GNUNET_TIME_Timestamp etime; + json_t *attrs; + + rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section, + provider_user_id, + legi_id, + &etime, + &attrs); + if (TALER_EC_NONE != rsc->ec) + return; + + { + json_t *val; + const char *name; + + json_object_foreach (attrs, name, val) + { + bool duplicate = false; + size_t idx; + json_t *str; + + json_array_foreach (rsc->attributes, idx, str) + { + if (0 == strcmp (json_string_value (str), + name)) + { + duplicate = true; + break; + } + } + if (duplicate) + continue; + GNUNET_assert (0 == + json_array_append (rsc->attributes, + json_string (name))); + } + } +} + + /** * Function implementing GET /reserves/$RID/attest transaction. * Execute a /reserves/ get attest. Given the public key of a reserve, @@ -75,26 +142,32 @@ reserve_attest_transaction (void *cls, struct ReserveAttestContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; -#if FIXME - qs = TEH_plugin->get_reserve_attributes (TEH_plugin->cls, - &rsc->reserve_pub, - &rsc->attributes); -#else - qs = GNUNET_DB_STATUS_HARD_ERROR; -#endif - if (GNUNET_DB_STATUS_HARD_ERROR == qs) + rsc->attributes = json_array (); + GNUNET_assert (NULL != rsc->attributes); + qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls, + &rsc->h_payto, + &kyc_process_cb, + rsc); + switch (qs) { + case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "get_reserve_attributes"); - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return qs; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return qs; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: rsc->not_found = true; - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + return qs; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: rsc->not_found = false; + break; + } return qs; } @@ -103,7 +176,9 @@ MHD_RESULT TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, const char *const args[1]) { - struct ReserveAttestContext rsc; + struct ReserveAttestContext rsc = { + .attributes = NULL + }; if (GNUNET_OK != GNUNET_STRINGS_string_to_data (args[0], @@ -117,6 +192,15 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, args[0]); } + { + char *payto_uri; + + payto_uri = TALER_reserve_make_payto (TEH_base_url, + &rsc.reserve_pub); + TALER_payto_hash (payto_uri, + &rsc.h_payto); + GNUNET_free (payto_uri); + } { MHD_RESULT mhd_ret; @@ -128,17 +212,26 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, &reserve_attest_transaction, &rsc)) { + json_decref (rsc.attributes); return mhd_ret; } } /* generate proper response */ if (rsc.not_found) { + json_decref (rsc.attributes); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, args[0]); } + if (TALER_EC_NONE != rsc.ec) + { + json_decref (rsc.attributes); + return TALER_MHD_reply_with_ec (rc->connection, + rsc.ec, + NULL); + } return TALER_MHD_REPLY_JSON_PACK ( rc->connection, MHD_HTTP_OK, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 609265f1e..e5cc8dfa7 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -877,6 +877,25 @@ typedef void const char *kyc_provider_section_name); +/** + * Function called on all legitimization operations + * we have performed for the given account so far + * (and that have not yet expired). + * + * @param cls closure + * @param kyc_provider_section_name configuration section + * of the respective KYC process + * @param provider_user_id UID at a provider (can be NULL) + * @param legi_id legitimization process ID (can be NULL) + */ +typedef void +(*TALER_EXCHANGEDB_LegitimizationProcessCallback)( + void *cls, + const char *kyc_provider_section_name, + const char *provider_user_id, + const char *legi_id); + + /** * Function called with information about the exchange's auditors. * @@ -5819,6 +5838,24 @@ struct TALER_EXCHANGEDB_Plugin void *spc_cls); + /** + * Call us on KYC legitimization processes satisfied and not expired for the + * given account. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param h_payto account identifier + * @param lpc function to call for each satisfied KYC legitimization process + * @param lpc_cls closure for @a lpc + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*iterate_kyc_reference)( + void *cls, + const struct TALER_PaytoHashP *h_payto, + TALER_EXCHANGEDB_LegitimizationProcessCallback lpc, + void *lpc_cls); + + /** * Call @a kac on withdrawn amounts after @a time_limit which are relevant * for a KYC trigger for a the (debited) account identified by @a h_payto. diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h index 2ff652f9e..7f4bf5b57 100644 --- a/src/include/taler_kyclogic_lib.h +++ b/src/include/taler_kyclogic_lib.h @@ -306,6 +306,27 @@ TALER_KYCLOGIC_requirements_to_logic (const char *requirements, const char **configuration_section); +/** + * Obtain attributes we collected about a user from a + * provider. + * + * @param provider_section configuration section of a + * provider that triggered KYC process for a user + * @param provider_user user ID of the user at the provider + * @param legitimization_id legitimizatin ID of a process + * of that user at the provider + * @param[out] attr_expiration set to when the @a attrs expire + * @param[out] attrs attributes we have about the user + * @return error code, #TALER_EC_NONE on success + */ +enum TALER_ErrorCode +TALER_KYCLOGIC_user_to_attributes (const char *provider_section, + const char *provider_user_id, + const char *legitimization_id, + struct GNUNET_TIME_Timestamp *attr_expiration, + json_t **attrs); + + /** * Obtain the provider logic for a given @a name. * diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index b04c24194..0c4a51124 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -1311,4 +1311,18 @@ TALER_KYCLOGIC_kyc_iterate_thresholds ( } +enum TALER_ErrorCode +TALER_KYCLOGIC_user_to_attributes (const char *provider_section, + const char *provider_user_id, + const char *legitimization_id, + struct GNUNET_TIME_Timestamp *attr_expiration, + json_t **attrs) +{ + GNUNET_break (0); // FIXME: not yet implemented!!! + *attrs = json_object (); + *attr_expiration = GNUNET_TIME_UNIT_ZERO_TS; + return TALER_EC_NONE; +} + + /* end of taler-exchange-httpd_kyc.c */ From 87ec6916c8d018566451ff941157d89e71dcd75a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Oct 2022 09:36:10 +0200 Subject: [PATCH 13/17] complete taler-exchange-httpd_reserves_attest.c logic (first pass, still without DB logic or tests) --- .../taler-exchange-httpd_reserves_attest.c | 154 ++++++++++++++++-- ...taler-exchange-httpd_reserves_get_attest.c | 2 +- 2 files changed, 140 insertions(+), 16 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c index 6a030dbd3..a740bb25a 100644 --- a/src/exchange/taler-exchange-httpd_reserves_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_attest.c @@ -23,9 +23,10 @@ #include "platform.h" #include #include -#include "taler_mhd_lib.h" -#include "taler_json_lib.h" #include "taler_dbevents.h" +#include "taler_kyclogic_lib.h" +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" #include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_reserves_attest.h" #include "taler-exchange-httpd_responses.h" @@ -49,6 +50,11 @@ struct ReserveAttestContext */ const struct TALER_ReservePublicKeyP *reserve_pub; + /** + * Hash of the payto URI of this reserve. + */ + struct TALER_PaytoHashP h_payto; + /** * Timestamp of the request. */ @@ -74,6 +80,16 @@ struct ReserveAttestContext */ json_t *json_attest; + /** + * Error code encountered in interaction with KYC provider. + */ + enum TALER_ErrorCode ec; + + /** + * Set to true if we did not find the reserve. + */ + bool not_found; + }; @@ -129,6 +145,79 @@ reply_reserve_attest_success (struct MHD_Connection *connection, } +/** + * Function called with information about all applicable + * legitimization processes for the given user. Finds the + * available attributes and merges them into our result + * set based on the details requested by the client. + * + * @param cls our `struct ReserveAttestContext *` + * @param provider_section KYC provider configuration section + * @param provider_user_id UID at a provider (can be NULL) + * @param legi_id legitimization process ID (can be NULL) + */ +static void +kyc_process_cb (void *cls, + const char *provider_section, + const char *provider_user_id, + const char *legi_id) +{ + struct ReserveAttestContext *rsc = cls; + struct GNUNET_TIME_Timestamp etime; + json_t *attrs; + bool match = false; + + rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section, + provider_user_id, + legi_id, + &etime, + &attrs); + if (TALER_EC_NONE != rsc->ec) + return; + if (GNUNET_TIME_absolute_is_past (etime.abs_time)) + { + json_decref (attrs); + return; + } + { + json_t *val; + const char *name; + + json_object_foreach (attrs, name, val) + { + bool requested = false; + size_t idx; + json_t *str; + + if (NULL != json_object_get (rsc->json_attest, + name)) + continue; /* duplicate */ + json_array_foreach (rsc->details, idx, str) + { + if (0 == strcmp (json_string_value (str), + name)) + { + requested = true; + break; + } + } + if (! requested) + continue; + match = true; + GNUNET_assert (0 == + json_object_set (rsc->json_attest, /* NOT set_new! */ + name, + val)); + } + } + json_decref (attrs); + if (! match) + return; + rsc->etime = GNUNET_TIME_timestamp_min (etime, + rsc->etime); +} + + /** * Function implementing /reserves/$RID/attest transaction. Given the public * key of a reserve, return the associated transaction attest. Runs the @@ -152,25 +241,32 @@ reserve_attest_transaction (void *cls, struct ReserveAttestContext *rsc = cls; enum GNUNET_DB_QueryStatus qs; -#if FIXME - qs = TEH_plugin->get_reserve_attest (TEH_plugin->cls, - rsc->reserve_pub, - &rsc->json_attest, - &etime); -#else - qs = GNUNET_DB_STATUS_HARD_ERROR; - (void) rsc; -#endif - if (GNUNET_DB_STATUS_HARD_ERROR == qs) + rsc->json_attest = json_array (); + GNUNET_assert (NULL != rsc->json_attest); + qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls, + &rsc->h_payto, + &kyc_process_cb, + rsc); + switch (qs) { + case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_attest"); + "iterate_kyc_reference"); + return qs; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return qs; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + rsc->not_found = true; + return qs; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + rsc->not_found = false; + break; } - // FIXME: filter json_attest by requested attributes! return qs; } @@ -180,7 +276,9 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, const struct TALER_ReservePublicKeyP *reserve_pub, const json_t *root) { - struct ReserveAttestContext rsc; + struct ReserveAttestContext rsc = { + .etime = GNUNET_TIME_UNIT_FOREVER_TS + }; MHD_RESULT mhd_ret; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("request_timestamp", @@ -235,6 +333,17 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, NULL); } + + { + char *payto_uri; + + payto_uri = TALER_reserve_make_payto (TEH_base_url, + rsc.reserve_pub); + TALER_payto_hash (payto_uri, + &rsc.h_payto); + GNUNET_free (payto_uri); + } + if (GNUNET_OK != TEH_DB_run_transaction (rc->connection, "post reserve attest", @@ -245,6 +354,21 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc, { return mhd_ret; } + if (rsc.not_found) + { + json_decref (rsc.json_attest); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + } + if (TALER_EC_NONE != rsc.ec) + { + json_decref (rsc.json_attest); + return TALER_MHD_reply_with_ec (rc->connection, + rsc.ec, + NULL); + } mhd_ret = reply_reserve_attest_success (rc->connection, &rsc); return mhd_ret; diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c index bd574acd2..aa0f26a4a 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get_attest.c +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -156,7 +156,7 @@ reserve_attest_transaction (void *cls, = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_attributes"); + "iterate_kyc_reference"); return qs; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); From f4c8eb6a9c460a9a9b8ea1c4358e1ce87097a8f1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Oct 2022 12:46:30 +0200 Subject: [PATCH 14/17] -skeletons for new PG functions --- src/exchangedb/Makefile.am | 4 +- src/exchangedb/pg_do_reserve_open.c | 68 ++++++++ src/exchangedb/pg_do_reserve_open.h | 55 +++++++ src/exchangedb/pg_helper.h | 149 ++++++++++++++++++ src/exchangedb/pg_insert_close_request.c | 64 ++++++++ src/exchangedb/pg_insert_close_request.h | 52 ++++++ .../pg_insert_reserve_open_deposit.c | 64 ++++++++ .../pg_insert_reserve_open_deposit.h | 52 ++++++ src/exchangedb/pg_iterate_kyc_reference.c | 58 +++++++ src/exchangedb/pg_iterate_kyc_reference.h | 46 ++++++ .../pg_iterate_reserve_close_info.c | 63 ++++++++ .../pg_iterate_reserve_close_info.h | 50 ++++++ src/exchangedb/pg_select_reserve_close_info.c | 62 ++++++++ src/exchangedb/pg_select_reserve_close_info.h | 49 ++++++ src/exchangedb/plugin_exchangedb_postgres.c | 137 +--------------- 15 files changed, 841 insertions(+), 132 deletions(-) create mode 100644 src/exchangedb/pg_do_reserve_open.c create mode 100644 src/exchangedb/pg_do_reserve_open.h create mode 100644 src/exchangedb/pg_helper.h create mode 100644 src/exchangedb/pg_insert_close_request.c create mode 100644 src/exchangedb/pg_insert_close_request.h create mode 100644 src/exchangedb/pg_insert_reserve_open_deposit.c create mode 100644 src/exchangedb/pg_insert_reserve_open_deposit.h create mode 100644 src/exchangedb/pg_iterate_kyc_reference.c create mode 100644 src/exchangedb/pg_iterate_kyc_reference.h create mode 100644 src/exchangedb/pg_iterate_reserve_close_info.c create mode 100644 src/exchangedb/pg_iterate_reserve_close_info.h create mode 100644 src/exchangedb/pg_select_reserve_close_info.c create mode 100644 src/exchangedb/pg_select_reserve_close_info.h diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 01ce13dec..3ea5a0d94 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -68,7 +68,9 @@ plugin_LTLIBRARIES = \ endif libtaler_plugin_exchangedb_postgres_la_SOURCES = \ - plugin_exchangedb_postgres.c + plugin_exchangedb_postgres.c pg_helper.h \ + pg_insert_close_request.c pg_insert_close_request.h \ + pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h libtaler_plugin_exchangedb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \ diff --git a/src/exchangedb/pg_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c new file mode 100644 index 000000000..e4f72845f --- /dev/null +++ b/src/exchangedb/pg_do_reserve_open.c @@ -0,0 +1,68 @@ +/* + 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 pg_do_reserve_open.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_do_reserve_open.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_do_reserve_open ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *total_paid, + uint32_t min_purse_limit, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Timestamp desired_expiration, + struct GNUNET_TIME_Timestamp now, + const struct TALER_Amount *open_fee, + struct TALER_Amount *open_cost, + const struct GNUNET_TIME_Timestamp *final_expiration) +{ + struct PostgresClosure *pg = cls; + // FIXME: everything from here is cut&paste + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), + GNUNET_PQ_query_param_uint64 (&known_coin_id), + GNUNET_PQ_query_param_auto_from_type (coin_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_sig), + TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "insert_reserve_open_deposit", + "SELECT " + " insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6);"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_reserve_open_deposit", + params, + rs); +} diff --git a/src/exchangedb/pg_do_reserve_open.h b/src/exchangedb/pg_do_reserve_open.h new file mode 100644 index 000000000..6e4c91fcd --- /dev/null +++ b/src/exchangedb/pg_do_reserve_open.h @@ -0,0 +1,55 @@ +/* + 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 pg_do_reserve_open.h + * @brief implementation of the do_reserve_open function + * @author Christian Grothoff + */ +#ifndef PG_DO_RESERVE_OPEN_H +#define PG_DO_RESERVE_OPEN_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Insert reserve close operation into database. + * + * @param cls closure + * @param reserve_pub which reserve is this about? + * @param execution_date when did we perform the transfer? + * @param receiver_account to which account do we transfer, in payto://-format + * @param wtid identifier for the wire transfer + * @param amount_with_fee amount we charged to the reserve + * @param closing_fee how high is the closing fee + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TEH_PG_do_reserve_open ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *total_paid, + uint32_t min_purse_limit, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Timestamp desired_expiration, + struct GNUNET_TIME_Timestamp now, + const struct TALER_Amount *open_fee, + struct TALER_Amount *open_cost, + const struct GNUNET_TIME_Timestamp *final_expiration); + + +#endif diff --git a/src/exchangedb/pg_helper.h b/src/exchangedb/pg_helper.h new file mode 100644 index 000000000..e0a4be49d --- /dev/null +++ b/src/exchangedb/pg_helper.h @@ -0,0 +1,149 @@ +/* + 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 pg_helper.h + * @brief shared internal definitions for postgres DB plugin + * @author Christian Grothoff + */ +#ifndef PG_HELPER_H +#define PG_HELPER_H + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Our configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Directory with SQL statements to run to create tables. + */ + char *sql_dir; + + /** + * After how long should idle reserves be closed? + */ + struct GNUNET_TIME_Relative idle_reserve_expiration_time; + + /** + * After how long should reserves that have seen withdraw operations + * be garbage collected? + */ + struct GNUNET_TIME_Relative legal_reserve_expiration_time; + + /** + * What delay should we introduce before ready transactions + * are actually aggregated? + */ + struct GNUNET_TIME_Relative aggregator_shift; + + /** + * Which currency should we assume all amounts to be in? + */ + char *currency; + + /** + * Our base URL. + */ + char *exchange_url; + + /** + * Postgres connection handle. + */ + struct GNUNET_PQ_Context *conn; + + /** + * Name of the current transaction, for debugging. + */ + const char *transaction_name; + + /** + * Counts how often we have established a fresh @e conn + * to the database. Used to re-prepare statements. + */ + unsigned long long prep_gen; + + /** + * Did we initialize the prepared statements + * for this session? (To be replaced with @e prep_gen.) + */ + bool init; + +}; + + +/** + * Prepares SQL statement @a sql under @a name for + * connection @a pg once. + * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure. + * + * @param pg a `struct PostgresClosure` + * @param name name to prepare the statement under + * @param sql actual SQL text + */ +#define PREPARE(pg,name,sql) \ + do { \ + static unsigned long long prep_cnt; \ + \ + if (prep_cnt < pg->prep_gen) \ + { \ + struct GNUNET_PQ_PreparedStatement ps[] = { \ + GNUNET_PQ_make_prepare (name, sql, 0), \ + GNUNET_PQ_PREPARED_STATEMENT_END \ + }; \ + \ + if (GNUNET_OK != \ + GNUNET_PQ_prepare_statements (pg->conn, \ + ps)) \ + { \ + GNUNET_break (0); \ + return GNUNET_DB_STATUS_HARD_ERROR; \ + } \ + prep_cnt = pg->prep_gen; \ + } \ + } while (0) + + +/** + * Wrapper macro to add the currency from the plugin's state + * when fetching amounts from the database. + * + * @param field name of the database field to fetch amount from + * @param[out] amountp pointer to amount to set + */ +#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount ( \ + field,pg->currency,amountp) + + +/** + * Wrapper macro to add the currency from the plugin's state + * when fetching amounts from the database. NBO variant. + * + * @param field name of the database field to fetch amount from + * @param[out] amountp pointer to amount to set + */ +#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \ + amountp) TALER_PQ_result_spec_amount_nbo ( \ + field,pg->currency,amountp) + + +#endif diff --git a/src/exchangedb/pg_insert_close_request.c b/src/exchangedb/pg_insert_close_request.c new file mode 100644 index 000000000..3622149a7 --- /dev/null +++ b/src/exchangedb/pg_insert_close_request.c @@ -0,0 +1,64 @@ +/* + 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 pg_insert_close_request.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_insert_close_request.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_insert_close_request ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *payto_uri, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_Amount *closing_fee, + struct TALER_Amount *final_balance) +{ + struct PostgresClosure *pg = cls; + // FIXME: deal with payto_uri and closing_fee!! + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_timestamp (&request_timestamp), + GNUNET_PQ_query_param_auto_from_type (reserve_sig), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("out_final_balance", + final_balance), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "call_account_close", + "SELECT " + " out_final_balance_val" + ",out_final_balance_frac" + " FROM exchange_do_close_request" + " ($1, $2, $3)"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "call_account_close", + params, + rs); +} diff --git a/src/exchangedb/pg_insert_close_request.h b/src/exchangedb/pg_insert_close_request.h new file mode 100644 index 000000000..09404094f --- /dev/null +++ b/src/exchangedb/pg_insert_close_request.h @@ -0,0 +1,52 @@ +/* + 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 pg_insert_close_request.h + * @brief implementation of the insert_close_request function + * @author Christian Grothoff + */ +#ifndef PG_INSERT_CLOSE_REQUEST_H +#define PG_INSERT_CLOSE_REQUEST_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Function called to initiate closure of an account. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param reserve_pub public key of the account to close + * @param payto_uri where to wire the funds + * @param reserve_sig signature affiming that the account is to be closed + * @param request_timestamp time of the close request (client-side?) + * @param closing_fee closing fee to charge + * @param[out] final_balance set to the final balance in the account that will be wired back to the origin account + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TEH_PG_insert_close_request ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *payto_uri, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_Amount *closing_fee, + struct TALER_Amount *final_balance); + + +#endif diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.c b/src/exchangedb/pg_insert_reserve_open_deposit.c new file mode 100644 index 000000000..c767bfeee --- /dev/null +++ b/src/exchangedb/pg_insert_reserve_open_deposit.c @@ -0,0 +1,64 @@ +/* + 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 pg_insert_reserve_open_deposit.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_insert_reserve_open_deposit.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_insert_reserve_open_deposit ( + void *cls, + const struct TALER_CoinPublicInfo *cpi, + const struct TALER_CoinSpendSignatureP *coin_sig, + uint64_t known_coin_id, + const struct TALER_Amount *coin_total, + const struct TALER_ReserveSignatureP *reserve_sig, + bool *insufficient_funds) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), + GNUNET_PQ_query_param_uint64 (&known_coin_id), + GNUNET_PQ_query_param_auto_from_type (coin_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_sig), + TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "insert_reserve_open_deposit", + "SELECT " + " insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6);"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_reserve_open_deposit", + params, + rs); +} diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.h b/src/exchangedb/pg_insert_reserve_open_deposit.h new file mode 100644 index 000000000..335fda52d --- /dev/null +++ b/src/exchangedb/pg_insert_reserve_open_deposit.h @@ -0,0 +1,52 @@ +/* + 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 pg_insert_reserve_open_deposit.h + * @brief implementation of the insert_reserve_open_deposit function + * @author Christian Grothoff + */ +#ifndef PG_INSERT_RESERVE_OPEN_DEPOSIT_H +#define PG_INSERT_RESERVE_OPEN_DEPOSIT_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Insert reserve open coin deposit data into database. + * Subtracts the @a coin_total from the coin's balance. + * + * @param cls closure + * @param cpi public information about the coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT + * @param known_coin_id ID of the coin in the known_coins table + * @param coin_total amount to be spent of the coin (including deposit fee) + * @param reserve_sig signature by the reserve affirming the open operation + * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false + * @return transaction status code, 0 if operation is already in the DB + */ +enum GNUNET_DB_QueryStatus +TEH_PG_insert_reserve_open_deposit ( + void *cls, + const struct TALER_CoinPublicInfo *cpi, + const struct TALER_CoinSpendSignatureP *coin_sig, + uint64_t known_coin_id, + const struct TALER_Amount *coin_total, + const struct TALER_ReserveSignatureP *reserve_sig, + bool *insufficient_funds); + +#endif diff --git a/src/exchangedb/pg_iterate_kyc_reference.c b/src/exchangedb/pg_iterate_kyc_reference.c new file mode 100644 index 000000000..e24a22bf2 --- /dev/null +++ b/src/exchangedb/pg_iterate_kyc_reference.c @@ -0,0 +1,58 @@ +/* + 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 pg_iterate_kyc_reference.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_iterate_kyc_reference.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_iterate_kyc_reference ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + TALER_EXCHANGEDB_LegitimizationProcessCallback lpc, + void *lpc_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_end + }; + // FIXME: everything from here is copy*paste + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "iterate_kyc_reference", + "SELECT " + " insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6);"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "iterate_kyc_reference", + params, + rs); +} diff --git a/src/exchangedb/pg_iterate_kyc_reference.h b/src/exchangedb/pg_iterate_kyc_reference.h new file mode 100644 index 000000000..0242fdcf1 --- /dev/null +++ b/src/exchangedb/pg_iterate_kyc_reference.h @@ -0,0 +1,46 @@ +/* + 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 pg_iterate_kyc_reference.h + * @brief implementation of the iterate_kyc_reference function + * @author Christian Grothoff + */ +#ifndef PG_ITERATE_KYC_REFERENCE_H +#define PG_ITERATE_KYC_REFERENCE_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Call us on KYC legitimization processes satisfied and not expired for the + * given account. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param h_payto account identifier + * @param lpc function to call for each satisfied KYC legitimization process + * @param lpc_cls closure for @a lpc + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +TEH_PG_iterate_kyc_reference ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + TALER_EXCHANGEDB_LegitimizationProcessCallback lpc, + void *lpc_cls); + +#endif diff --git a/src/exchangedb/pg_iterate_reserve_close_info.c b/src/exchangedb/pg_iterate_reserve_close_info.c new file mode 100644 index 000000000..e9a5f6648 --- /dev/null +++ b/src/exchangedb/pg_iterate_reserve_close_info.c @@ -0,0 +1,63 @@ +/* + 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 pg_iterate_reserve_close_info.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_insert_reserve_open_deposit.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_iterate_reserve_close_info ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls) +{ + struct PostgresClosure *pg = cls; + // FIXME: everything from here is copy&paste + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), + GNUNET_PQ_query_param_uint64 (&known_coin_id), + GNUNET_PQ_query_param_auto_from_type (coin_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_sig), + TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "insert_reserve_open_deposit", + "SELECT " + " insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6);"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_reserve_open_deposit", + params, + rs); +} diff --git a/src/exchangedb/pg_iterate_reserve_close_info.h b/src/exchangedb/pg_iterate_reserve_close_info.h new file mode 100644 index 000000000..a7e7c8d4a --- /dev/null +++ b/src/exchangedb/pg_iterate_reserve_close_info.h @@ -0,0 +1,50 @@ +/* + 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 pg_iterate_reserve_close_info.h + * @brief implementation of the iterate_reserve_close_info function + * @author Christian Grothoff + */ +#ifndef PG_ITERATE_RESERVE_CLOSE_INFO_H +#define PG_ITERATE_RESERVE_CLOSE_INFO_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Select information needed for KYC checks on reserve close: historic + * reserve closures going to the same account. + * + * @param cls closure + * @param h_payto which target account is this about? + * @param h_payto account identifier + * @param time_limit oldest transaction that could be relevant + * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK). + * @param kac_cls closure for @a kac + * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error + */ +enum GNUNET_DB_QueryStatus +TEH_PG_iterate_reserve_close_info ( + void *cls, + const struct TALER_PaytoHashP *h_payto, + struct GNUNET_TIME_Absolute time_limit, + TALER_EXCHANGEDB_KycAmountCallback kac, + void *kac_cls); + + +#endif diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c new file mode 100644 index 000000000..472ec27c9 --- /dev/null +++ b/src/exchangedb/pg_select_reserve_close_info.c @@ -0,0 +1,62 @@ +/* + 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 pg_select_reserve_close_info.c + * @brief Low-level (statement-level) Postgres database access for the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_insert_reserve_open_deposit.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_select_reserve_close_info ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, + char **payto_uri) +{ + struct PostgresClosure *pg = cls; + // FIXME: everything from here is copy*paste! + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), + GNUNET_PQ_query_param_uint64 (&known_coin_id), + GNUNET_PQ_query_param_auto_from_type (coin_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_sig), + TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "insert_reserve_open_deposit", + "SELECT " + " insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6);"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "insert_reserve_open_deposit", + params, + rs); +} diff --git a/src/exchangedb/pg_select_reserve_close_info.h b/src/exchangedb/pg_select_reserve_close_info.h new file mode 100644 index 000000000..2b90ffd05 --- /dev/null +++ b/src/exchangedb/pg_select_reserve_close_info.h @@ -0,0 +1,49 @@ +/* + 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 pg_select_reserve_close_info.h + * @brief implementation of the select_reserve_close_info function + * @author Christian Grothoff + */ +#ifndef PG_SELECT_RESERVE_CLOSE_INFO_H +#define PG_SELECT_RESERVE_CLOSE_INFO_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Select information needed to see if we can close + * a reserve. + * + * @param cls closure + * @param reserve_pub which reserve is this about? + * @param[out] balance current reserve balance + * @param[out] payto_uri set to URL of account that + * originally funded the reserve; + * could be set to NULL if not known + * @return transaction status code, 0 if reserve unknown + */ +enum GNUNET_DB_QueryStatus +TEH_PG_select_reserve_close_info ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_Amount *balance, + char **payto_uri); + + +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index e3da5216b..341364f9f 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -29,6 +29,9 @@ #include "taler_util.h" #include "taler_json_lib.h" #include "taler_exchangedb_plugin.h" +#include "pg_helper.h" +#include "pg_insert_close_request.h" +#include "pg_insert_reserve_open_deposit.h" #include #include #include @@ -42,26 +45,6 @@ */ #define AUTO_EXPLAIN 1 -/** - * Wrapper macro to add the currency from the plugin's state - * when fetching amounts from the database. - * - * @param field name of the database field to fetch amount from - * @param[out] amountp pointer to amount to set - */ -#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount ( \ - field,pg->currency,amountp) - -/** - * Wrapper macro to add the currency from the plugin's state - * when fetching amounts from the database. NBO variant. - * - * @param field name of the database field to fetch amount from - * @param[out] amountp pointer to amount to set - */ -#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \ - amountp) TALER_PQ_result_spec_amount_nbo ( \ - field,pg->currency,amountp) /** * Log a really unexpected PQ error with all the details we can get hold of. @@ -81,69 +64,6 @@ } while (0) -/** - * Type of the "cls" argument given to each of the functions in - * our API. - */ -struct PostgresClosure -{ - - /** - * Our configuration. - */ - const struct GNUNET_CONFIGURATION_Handle *cfg; - - /** - * Directory with SQL statements to run to create tables. - */ - char *sql_dir; - - /** - * After how long should idle reserves be closed? - */ - struct GNUNET_TIME_Relative idle_reserve_expiration_time; - - /** - * After how long should reserves that have seen withdraw operations - * be garbage collected? - */ - struct GNUNET_TIME_Relative legal_reserve_expiration_time; - - /** - * What delay should we introduce before ready transactions - * are actually aggregated? - */ - struct GNUNET_TIME_Relative aggregator_shift; - - /** - * Which currency should we assume all amounts to be in? - */ - char *currency; - - /** - * Our base URL. - */ - char *exchange_url; - - /** - * Postgres connection handle. - */ - struct GNUNET_PQ_Context *conn; - - /** - * Name of the current transaction, for debugging. - */ - const char *transaction_name; - - /** - * Did we initialize the prepared statements - * for this session? - */ - bool init; - -}; - - /** * Drop all Taler tables. This should only be used by testcases. * @@ -4467,15 +4387,7 @@ prepare_statements (struct PostgresClosure *pg) " FROM exchange_do_history_request" " ($1, $2, $3, $4, $5)", 5), - /* Used in #postgres_insert_close_request() */ - GNUNET_PQ_make_prepare ( - "call_account_close", - "SELECT " - " out_final_balance_val" - ",out_final_balance_frac" - " FROM exchange_do_close_request" - " ($1, $2, $3)", - 3), + /* Used in #postgres_insert_kyc_requirement_for_account() */ GNUNET_PQ_make_prepare ( "insert_legitimization_requirement", @@ -4674,6 +4586,7 @@ internal_setup (struct PostgresClosure *pg, NULL); if (NULL == db_conn) return GNUNET_SYSERR; + pg->prep_gen++; pg->conn = db_conn; } if (NULL == pg->transaction_name) @@ -16236,44 +16149,6 @@ postgres_insert_history_request ( } -/** - * Function called to initiate closure of an account. - * - * @param cls the @e cls of this struct with the plugin-specific state - * @param reserve_pub public key of the account to close - * @param reserve_sig signature affiming that the account is to be closed - * @param request_timestamp time of the close request (client-side?) - * @param[out] final_balance set to the final balance in the account that will be wired back to the origin account - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -postgres_insert_close_request ( - void *cls, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_ReserveSignatureP *reserve_sig, - struct GNUNET_TIME_Timestamp request_timestamp, - struct TALER_Amount *final_balance) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (reserve_pub), - GNUNET_PQ_query_param_timestamp (&request_timestamp), - GNUNET_PQ_query_param_auto_from_type (reserve_sig), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("out_final_balance", - final_balance), - GNUNET_PQ_result_spec_end - }; - - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "call_account_close", - params, - rs); -} - - /** * Function called to persist a request to drain profits. * @@ -17390,7 +17265,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->insert_history_request = &postgres_insert_history_request; plugin->insert_close_request - = &postgres_insert_close_request; + = &TEH_PG_insert_close_request; plugin->insert_drain_profit = &postgres_insert_drain_profit; plugin->profit_drains_get_pending From 2dbf8cefe0a11252758227bf4a3a7881fa663edc Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Oct 2022 17:05:29 +0200 Subject: [PATCH 15/17] -work on DB logic --- contrib/gana | 2 +- src/exchangedb/Makefile.am | 5 +- src/exchangedb/pg_iterate_kyc_reference.c | 95 ++++++++++++++-- .../pg_iterate_reserve_close_info.c | 103 ++++++++++++++---- src/exchangedb/pg_select_reserve_close_info.c | 25 ++--- 5 files changed, 184 insertions(+), 46 deletions(-) diff --git a/contrib/gana b/contrib/gana index 57d96e8e1..d402af78f 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 57d96e8e123df90c804a821874fc6cb88671ab75 +Subproject commit d402af78f6d360841db53baa46dddae13590ec33 diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 3ea5a0d94..e446682b9 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -70,7 +70,10 @@ endif libtaler_plugin_exchangedb_postgres_la_SOURCES = \ plugin_exchangedb_postgres.c pg_helper.h \ pg_insert_close_request.c pg_insert_close_request.h \ - pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h + pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h \ + pg_iterate_kyc_reference.c pg_iterate_kyc_reference.h \ + pg_iterate_reserve_close_info.c pg_iterate_reserve_close_info.h \ + pg_select_reserve_close_info.c pg_select_reserve_close_info.h libtaler_plugin_exchangedb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \ diff --git a/src/exchangedb/pg_iterate_kyc_reference.c b/src/exchangedb/pg_iterate_kyc_reference.c index e24a22bf2..4a94722c4 100644 --- a/src/exchangedb/pg_iterate_kyc_reference.c +++ b/src/exchangedb/pg_iterate_kyc_reference.c @@ -26,6 +26,75 @@ #include "pg_helper.h" +/** + * Closure for #iterate_kyc_reference_cb() + */ +struct IteratorContext +{ + /** + * Function to call with the results. + */ + TALER_EXCHANGEDB_LegitimizationProcessCallback cb; + + /** + * Closure to pass to @e cb + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; +}; + + +/** + * Helper function for #TEH_PG_iterate_kyc_reference(). + * Calls the callback with each denomination key. + * + * @param cls a `struct IteratorContext` + * @param result db results + * @param num_results number of results in @a result + */ +static void +iterate_kyc_reference_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct IteratorContext *ic = cls; + + for (unsigned int i = 0; icb (ic->cb_cls, + kyc_provider_section_name, + provider_user_id, + legitimization_id); + GNUNET_PQ_cleanup_result (rs); + } +} + + enum GNUNET_DB_QueryStatus TEH_PG_iterate_kyc_reference ( void *cls, @@ -38,21 +107,23 @@ TEH_PG_iterate_kyc_reference ( GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_end }; - // FIXME: everything from here is copy*paste - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("insufficient_funds", - insufficient_funds), - GNUNET_PQ_result_spec_end + struct IteratorContext ic = { + .cb = lpc, + .cb_cls = lpc_cls, + .pg = pg }; PREPARE (pg, "iterate_kyc_reference", "SELECT " - " insufficient_funds" - " FROM exchange_do_reserve_open_deposit" - " ($1,$2,$3,$4,$5,$6);"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "iterate_kyc_reference", - params, - rs); + " section_name" + ",provider_user_id" + ",legi_id" + " FROM FIXME" + " WHERE h_payto=$1;"); + return GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "iterate_kyc_reference", + params, + &iterate_kyc_reference_cb, + &ic); } diff --git a/src/exchangedb/pg_iterate_reserve_close_info.c b/src/exchangedb/pg_iterate_reserve_close_info.c index e9a5f6648..c5ff61322 100644 --- a/src/exchangedb/pg_iterate_reserve_close_info.c +++ b/src/exchangedb/pg_iterate_reserve_close_info.c @@ -25,6 +25,70 @@ #include "pg_insert_reserve_open_deposit.h" #include "pg_helper.h" +/** + * Closure for #iterate_reserve_close_info_cb() + */ +struct IteratorContext +{ + /** + * Function to call with the results. + */ + TALER_EXCHANGEDB_KycAmountCallback cb; + + /** + * Closure to pass to @e cb + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; +}; + + +/** + * Helper function for #TEH_PG_iterate_reserve_close_info(). + * Calls the callback with each denomination key. + * + * @param cls a `struct IteratorContext` + * @param result db results + * @param num_results number of results in @a result + */ +static void +iterate_reserve_close_info_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct IteratorContext *ic = cls; + struct PostgresClosure *pg = ic->pg; + + for (unsigned int i = 0; icb (ic->cb_cls, + &amount, + ts); + } +} + enum GNUNET_DB_QueryStatus TEH_PG_iterate_reserve_close_info ( @@ -35,29 +99,30 @@ TEH_PG_iterate_reserve_close_info ( void *kac_cls) { struct PostgresClosure *pg = cls; - // FIXME: everything from here is copy&paste struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), - GNUNET_PQ_query_param_uint64 (&known_coin_id), - GNUNET_PQ_query_param_auto_from_type (coin_sig), - GNUNET_PQ_query_param_auto_from_type (reserve_sig), - TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_absolute_time (&time_limit), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("insufficient_funds", - insufficient_funds), - GNUNET_PQ_result_spec_end + struct IteratorContext ic = { + .cb = kac, + .cb_cls = kac_cls, + .pg = pg }; PREPARE (pg, - "insert_reserve_open_deposit", - "SELECT " - " insufficient_funds" - " FROM exchange_do_reserve_open_deposit" - " ($1,$2,$3,$4,$5,$6);"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_reserve_open_deposit", - params, - rs); + "iterate_reserve_close_info", + "SELECT" + " amount_val" + ",amount_frac" + ",timestamp" + " FROM FIXME" + " WHERE h_payto=$1" + " AND timestamp >= $2" + " ORDER BY timestamp DESC"); + return GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "iterate_reserve_close_info", + params, + &iterate_reserve_close_info_cb, + &ic); } diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c index 472ec27c9..a573f100d 100644 --- a/src/exchangedb/pg_select_reserve_close_info.c +++ b/src/exchangedb/pg_select_reserve_close_info.c @@ -34,29 +34,28 @@ TEH_PG_select_reserve_close_info ( char **payto_uri) { struct PostgresClosure *pg = cls; - // FIXME: everything from here is copy*paste! struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), - GNUNET_PQ_query_param_uint64 (&known_coin_id), - GNUNET_PQ_query_param_auto_from_type (coin_sig), - GNUNET_PQ_query_param_auto_from_type (reserve_sig), - TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("insufficient_funds", - insufficient_funds), + TALER_PQ_RESULT_SPEC_AMOUNT ("balance", + balance), + GNUNET_PQ_result_spec_string ("payto_uri", + payto_uri), GNUNET_PQ_result_spec_end }; PREPARE (pg, - "insert_reserve_open_deposit", + "select_reserve_close_info", "SELECT " - " insufficient_funds" - " FROM exchange_do_reserve_open_deposit" - " ($1,$2,$3,$4,$5,$6);"); + " balance_frac" + ",balance_val" + ",payto_uri" + " FROM FIXME" + " WHERE reserve_pub=$1;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_reserve_open_deposit", + "select_reserve_close_info", params, rs); } From 4a487b179c013886721c4aa06af9c75e9aad508c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Oct 2022 19:34:11 +0200 Subject: [PATCH 16/17] -implement sketch for pg_do_reserve_open.c --- contrib/gana | 2 +- src/exchangedb/Makefile.am | 3 +- src/exchangedb/pg_do_reserve_open.c | 31 +++++++++++-------- src/exchangedb/pg_do_reserve_open.h | 2 +- src/exchangedb/pg_select_reserve_close_info.c | 2 +- src/include/taler_exchangedb_plugin.h | 2 +- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/contrib/gana b/contrib/gana index d402af78f..57d96e8e1 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit d402af78f6d360841db53baa46dddae13590ec33 +Subproject commit 57d96e8e123df90c804a821874fc6cb88671ab75 diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index e446682b9..1fc89d1b5 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -69,11 +69,12 @@ endif libtaler_plugin_exchangedb_postgres_la_SOURCES = \ plugin_exchangedb_postgres.c pg_helper.h \ + pg_do_reserve_open.c pg_do_reserve_open.h \ pg_insert_close_request.c pg_insert_close_request.h \ pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h \ pg_iterate_kyc_reference.c pg_iterate_kyc_reference.h \ pg_iterate_reserve_close_info.c pg_iterate_reserve_close_info.h \ - pg_select_reserve_close_info.c pg_select_reserve_close_info.h + pg_select_reserve_close_info.c pg_select_reserve_close_info.h libtaler_plugin_exchangedb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \ diff --git a/src/exchangedb/pg_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c index e4f72845f..bd4f72401 100644 --- a/src/exchangedb/pg_do_reserve_open.c +++ b/src/exchangedb/pg_do_reserve_open.c @@ -37,32 +37,37 @@ TEH_PG_do_reserve_open ( struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, struct TALER_Amount *open_cost, - const struct GNUNET_TIME_Timestamp *final_expiration) + struct GNUNET_TIME_Timestamp *final_expiration) { struct PostgresClosure *pg = cls; - // FIXME: everything from here is cut&paste struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub), - GNUNET_PQ_query_param_uint64 (&known_coin_id), - GNUNET_PQ_query_param_auto_from_type (coin_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + TALER_PQ_query_param_amount (total_paid), + GNUNET_PQ_query_param_uint32 (&min_purse_limit), GNUNET_PQ_query_param_auto_from_type (reserve_sig), - TALER_PQ_query_param_amount (coin_total), + GNUNET_PQ_query_param_timestamp (&desired_expiration), + GNUNET_PQ_query_param_timestamp (&now), + TALER_PQ_query_param_amount (open_fee), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("insufficient_funds", - insufficient_funds), + TALER_PQ_RESULT_SPEC_AMOUNT ("open_cost", + open_cost), + GNUNET_PQ_result_spec_timestamp ("final_expiration", + final_expiration), GNUNET_PQ_result_spec_end }; PREPARE (pg, - "insert_reserve_open_deposit", + "do_reserve_open", "SELECT " - " insufficient_funds" - " FROM exchange_do_reserve_open_deposit" - " ($1,$2,$3,$4,$5,$6);"); + " open_cost_val" + ",open_cost_frac" + ",final_expiration" + " FROM exchange_do_reserve_open" + " ($1,$2,$3,$4,$5,$6,$7,$8,$9);"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_reserve_open_deposit", + "do_reserve_open", params, rs); } diff --git a/src/exchangedb/pg_do_reserve_open.h b/src/exchangedb/pg_do_reserve_open.h index 6e4c91fcd..aeef59eb0 100644 --- a/src/exchangedb/pg_do_reserve_open.h +++ b/src/exchangedb/pg_do_reserve_open.h @@ -49,7 +49,7 @@ TEH_PG_do_reserve_open ( struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, struct TALER_Amount *open_cost, - const struct GNUNET_TIME_Timestamp *final_expiration); + struct GNUNET_TIME_Timestamp *final_expiration); #endif diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c index a573f100d..53ea45682 100644 --- a/src/exchangedb/pg_select_reserve_close_info.c +++ b/src/exchangedb/pg_select_reserve_close_info.c @@ -22,7 +22,7 @@ #include "taler_error_codes.h" #include "taler_dbevents.h" #include "taler_pq_lib.h" -#include "pg_insert_reserve_open_deposit.h" +#include "pg_select_reserve_close_info.h" #include "pg_helper.h" diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index e5cc8dfa7..bde3c8136 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4108,7 +4108,7 @@ struct TALER_EXCHANGEDB_Plugin struct GNUNET_TIME_Timestamp now, const struct TALER_Amount *open_fee, struct TALER_Amount *open_cost, - const struct GNUNET_TIME_Timestamp *final_expiration); + struct GNUNET_TIME_Timestamp *final_expiration); /** From 856b8e26c2b83ebce31eb35c9fc9f23641187be9 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Oct 2022 23:54:12 +0200 Subject: [PATCH 17/17] -more work on new DB logic --- .../taler-exchange-httpd_reserves_close.c | 5 +- .../taler-exchange-httpd_reserves_open.c | 1 + src/exchangedb/common-0001.sql | 8 +- src/exchangedb/pg_insert_close_request.c | 39 +++--- src/exchangedb/pg_insert_close_request.h | 6 +- .../pg_insert_reserve_open_deposit.c | 8 +- .../pg_insert_reserve_open_deposit.h | 2 + src/exchangedb/pg_iterate_kyc_reference.c | 10 +- .../pg_iterate_reserve_close_info.c | 23 ++-- src/exchangedb/pg_select_reserve_close_info.c | 8 +- src/exchangedb/plugin_exchangedb_postgres.c | 15 ++- src/exchangedb/procedures.sql | 119 ++++++++++-------- src/include/taler_exchangedb_plugin.h | 8 +- 13 files changed, 148 insertions(+), 104 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c index 6d998bb93..be36f1e73 100644 --- a/src/exchange/taler-exchange-httpd_reserves_close.c +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -297,14 +297,13 @@ reserve_close_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } - qs = TEH_plugin->insert_close_request (TEH_plugin->cls, rcc->reserve_pub, payto_uri, &rcc->reserve_sig, rcc->timestamp, - &wf->closing, - &rcc->wire_amount); + &balance, + &wf->closing); GNUNET_free (payto_uri); if (GNUNET_DB_STATUS_HARD_ERROR == qs) { diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index c9f5e4019..d446d9b40 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -191,6 +191,7 @@ reserve_open_transaction (void *cls, coin->known_coin_id, &coin->amount, &rsc->reserve_sig, + rsc->reserve_pub, &insufficient_funds); /* 0 == qs is fine, then the coin was already spent for this very operation as identified diff --git a/src/exchangedb/common-0001.sql b/src/exchangedb/common-0001.sql index 4a0aac381..564bf3b35 100644 --- a/src/exchangedb/common-0001.sql +++ b/src/exchangedb/common-0001.sql @@ -459,7 +459,8 @@ BEGIN PERFORM create_partitioned_table( 'CREATE TABLE IF NOT EXISTS %I' '(reserve_open_deposit_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE / PRIMARY KEY' - ',reserve_pub BYTEA NOT NULL' -- REFERENCES reserves (reserve_pub) ON DELETE CASCADE' + ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)' + ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=32)' ',request_timestamp INT8 NOT NULL' ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)' @@ -496,7 +497,7 @@ BEGIN EXECUTE FORMAT ( 'ALTER TABLE reserves_open_deposits_' || partition_suffix || ' ' 'ADD CONSTRAINT reserves_open_deposits_' || partition_suffix || '_coin_unique ' - 'PRIMARY KEY (coin_pub,reserve_pub)' + 'PRIMARY KEY (coin_pub,coin_sig)' ); END $$; @@ -1749,6 +1750,9 @@ BEGIN ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)' ',close_val INT8 NOT NULL' ',close_frac INT4 NOT NULL' + ',close_fee_val INT8 NOT NULL' + ',close_fee_frac INT4 NOT NULL' + ',payto_uri VARCHAR NOT NULL' ',PRIMARY KEY (reserve_pub,close_timestamp)' ') %s ;' ,table_name diff --git a/src/exchangedb/pg_insert_close_request.c b/src/exchangedb/pg_insert_close_request.c index 3622149a7..43ca944f4 100644 --- a/src/exchangedb/pg_insert_close_request.c +++ b/src/exchangedb/pg_insert_close_request.c @@ -33,32 +33,35 @@ TEH_PG_insert_close_request ( const char *payto_uri, const struct TALER_ReserveSignatureP *reserve_sig, struct GNUNET_TIME_Timestamp request_timestamp, - const struct TALER_Amount *closing_fee, - struct TALER_Amount *final_balance) + const struct TALER_Amount *balance, + const struct TALER_Amount *closing_fee) { struct PostgresClosure *pg = cls; - // FIXME: deal with payto_uri and closing_fee!! struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (reserve_pub), GNUNET_PQ_query_param_timestamp (&request_timestamp), GNUNET_PQ_query_param_auto_from_type (reserve_sig), + TALER_PQ_query_param_amount (balance), + TALER_PQ_query_param_amount (closing_fee), + GNUNET_PQ_query_param_string (payto_uri), GNUNET_PQ_query_param_end }; - struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("out_final_balance", - final_balance), - GNUNET_PQ_result_spec_end - }; PREPARE (pg, - "call_account_close", - "SELECT " - " out_final_balance_val" - ",out_final_balance_frac" - " FROM exchange_do_close_request" - " ($1, $2, $3)"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "call_account_close", - params, - rs); + "insert_account_close", + "INSERT INTO close_requests" + "(reserve_pub" + ",close_timestamp" + ",reserve_sig" + ",close_val" + ",close_frac," + ",close_fee_val" + ",close_fee_frac" + ",payto_uri" + ")" + "VALUES ($1, $2, $3, $4, $5, $6, $7)" + " ON CONFLICT DO NOTHING;"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_account_close", + params); } diff --git a/src/exchangedb/pg_insert_close_request.h b/src/exchangedb/pg_insert_close_request.h index 09404094f..c014a10b9 100644 --- a/src/exchangedb/pg_insert_close_request.h +++ b/src/exchangedb/pg_insert_close_request.h @@ -34,8 +34,8 @@ * @param payto_uri where to wire the funds * @param reserve_sig signature affiming that the account is to be closed * @param request_timestamp time of the close request (client-side?) + * @param balance final balance in the reserve * @param closing_fee closing fee to charge - * @param[out] final_balance set to the final balance in the account that will be wired back to the origin account * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -45,8 +45,8 @@ TEH_PG_insert_close_request ( const char *payto_uri, const struct TALER_ReserveSignatureP *reserve_sig, struct GNUNET_TIME_Timestamp request_timestamp, - const struct TALER_Amount *closing_fee, - struct TALER_Amount *final_balance); + const struct TALER_Amount *balance, + const struct TALER_Amount *closing_fee); #endif diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.c b/src/exchangedb/pg_insert_reserve_open_deposit.c index c767bfeee..8bf70e7b2 100644 --- a/src/exchangedb/pg_insert_reserve_open_deposit.c +++ b/src/exchangedb/pg_insert_reserve_open_deposit.c @@ -34,6 +34,7 @@ TEH_PG_insert_reserve_open_deposit ( uint64_t known_coin_id, const struct TALER_Amount *coin_total, const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, bool *insufficient_funds) { struct PostgresClosure *pg = cls; @@ -42,11 +43,12 @@ TEH_PG_insert_reserve_open_deposit ( GNUNET_PQ_query_param_uint64 (&known_coin_id), GNUNET_PQ_query_param_auto_from_type (coin_sig), GNUNET_PQ_query_param_auto_from_type (reserve_sig), + GNUNET_PQ_query_param_auto_from_type (reserve_pub), TALER_PQ_query_param_amount (coin_total), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_bool ("insufficient_funds", + GNUNET_PQ_result_spec_bool ("out_insufficient_funds", insufficient_funds), GNUNET_PQ_result_spec_end }; @@ -54,9 +56,9 @@ TEH_PG_insert_reserve_open_deposit ( PREPARE (pg, "insert_reserve_open_deposit", "SELECT " - " insufficient_funds" + " out_insufficient_funds" " FROM exchange_do_reserve_open_deposit" - " ($1,$2,$3,$4,$5,$6);"); + " ($1,$2,$3,$4,$5,$6,$7);"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "insert_reserve_open_deposit", params, diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.h b/src/exchangedb/pg_insert_reserve_open_deposit.h index 335fda52d..7eb2fe093 100644 --- a/src/exchangedb/pg_insert_reserve_open_deposit.h +++ b/src/exchangedb/pg_insert_reserve_open_deposit.h @@ -36,6 +36,7 @@ * @param known_coin_id ID of the coin in the known_coins table * @param coin_total amount to be spent of the coin (including deposit fee) * @param reserve_sig signature by the reserve affirming the open operation + * @param reserve_pub public key of the reserve being opened * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false * @return transaction status code, 0 if operation is already in the DB */ @@ -47,6 +48,7 @@ TEH_PG_insert_reserve_open_deposit ( uint64_t known_coin_id, const struct TALER_Amount *coin_total, const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, bool *insufficient_funds); #endif diff --git a/src/exchangedb/pg_iterate_kyc_reference.c b/src/exchangedb/pg_iterate_kyc_reference.c index 4a94722c4..772c51e2c 100644 --- a/src/exchangedb/pg_iterate_kyc_reference.c +++ b/src/exchangedb/pg_iterate_kyc_reference.c @@ -69,11 +69,11 @@ iterate_kyc_reference_cb (void *cls, char *provider_user_id; char *legitimization_id; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_string ("section_name", + GNUNET_PQ_result_spec_string ("provider_section", &kyc_provider_section_name), GNUNET_PQ_result_spec_string ("provider_user_id", &provider_user_id), - GNUNET_PQ_result_spec_string ("legi_id", + GNUNET_PQ_result_spec_string ("provider_legitimization_id", &legitimization_id), GNUNET_PQ_result_spec_end }; @@ -116,10 +116,10 @@ TEH_PG_iterate_kyc_reference ( PREPARE (pg, "iterate_kyc_reference", "SELECT " - " section_name" + " provider_section" ",provider_user_id" - ",legi_id" - " FROM FIXME" + ",provider_legitimization_id" + " FROM legitimization_processes" " WHERE h_payto=$1;"); return GNUNET_PQ_eval_prepared_multi_select (pg->conn, "iterate_kyc_reference", diff --git a/src/exchangedb/pg_iterate_reserve_close_info.c b/src/exchangedb/pg_iterate_reserve_close_info.c index c5ff61322..f1b2d452b 100644 --- a/src/exchangedb/pg_iterate_reserve_close_info.c +++ b/src/exchangedb/pg_iterate_reserve_close_info.c @@ -68,7 +68,7 @@ iterate_reserve_close_info_cb (void *cls, struct TALER_Amount amount; struct GNUNET_TIME_Absolute ts; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_absolute_time ("timestamp", + GNUNET_PQ_result_spec_absolute_time ("execution_date", &ts), TALER_PQ_RESULT_SPEC_AMOUNT ("amount", &amount), @@ -115,14 +115,15 @@ TEH_PG_iterate_reserve_close_info ( "SELECT" " amount_val" ",amount_frac" - ",timestamp" - " FROM FIXME" - " WHERE h_payto=$1" - " AND timestamp >= $2" - " ORDER BY timestamp DESC"); - return GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "iterate_reserve_close_info", - params, - &iterate_reserve_close_info_cb, - &ic); + ",execution_date" + " FROM reserves_close" + " WHERE wire_target_h_payto=$1" + " AND execution_date >= $2" + " ORDER BY execution_date DESC"); + return GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "iterate_reserve_close_info", + params, + &iterate_reserve_close_info_cb, + &ic); } diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c index 53ea45682..0b373b7bb 100644 --- a/src/exchangedb/pg_select_reserve_close_info.c +++ b/src/exchangedb/pg_select_reserve_close_info.c @@ -39,7 +39,7 @@ TEH_PG_select_reserve_close_info ( GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_AMOUNT ("balance", + TALER_PQ_RESULT_SPEC_AMOUNT ("close", balance), GNUNET_PQ_result_spec_string ("payto_uri", payto_uri), @@ -49,10 +49,10 @@ TEH_PG_select_reserve_close_info ( PREPARE (pg, "select_reserve_close_info", "SELECT " - " balance_frac" - ",balance_val" + " close_frac" + ",close_val" ",payto_uri" - " FROM FIXME" + " FROM close_requests" " WHERE reserve_pub=$1;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "select_reserve_close_info", diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 341364f9f..d2e2eb5df 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -32,6 +32,9 @@ #include "pg_helper.h" #include "pg_insert_close_request.h" #include "pg_insert_reserve_open_deposit.h" +#include "pg_iterate_kyc_reference.h" +#include "pg_iterate_reserve_close_info.h" +#include "pg_select_reserve_close_info.h" #include #include #include @@ -17264,8 +17267,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_select_purse_merge; plugin->insert_history_request = &postgres_insert_history_request; - plugin->insert_close_request - = &TEH_PG_insert_close_request; plugin->insert_drain_profit = &postgres_insert_drain_profit; plugin->profit_drains_get_pending @@ -17294,6 +17295,16 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_select_aggregation_amounts_for_kyc_check; plugin->select_merge_amounts_for_kyc_check = &postgres_select_merge_amounts_for_kyc_check; + /* NEW style, sort alphabetically! */ + plugin->insert_close_request + = &TEH_PG_insert_close_request; + plugin->iterate_reserve_close_info + = &TEH_PG_iterate_reserve_close_info; + plugin->iterate_kyc_reference + = &TEH_PG_iterate_kyc_reference; + plugin->select_reserve_close_info + = &TEH_PG_select_reserve_close_info; + return plugin; } diff --git a/src/exchangedb/procedures.sql b/src/exchangedb/procedures.sql index 1940fa7b8..8407f20c7 100644 --- a/src/exchangedb/procedures.sql +++ b/src/exchangedb/procedures.sql @@ -1778,7 +1778,7 @@ ELSE my_amount_val = my_amount_val + my_amount_frac / 100000000; my_amount_frac = my_amount_frac % 100000000; - UPDATE reserves + UPDATE exchange.reserves SET current_balance_frac=current_balance_frac+my_amount_frac - CASE @@ -1795,7 +1795,7 @@ ELSE WHERE reserve_pub=in_reserve_pub; -- ... and mark purse as finished. - UPDATE purse_requests + UPDATE exchange.purse_requests SET finished=true WHERE purse_pub=in_purse_pub; END IF; @@ -1881,7 +1881,7 @@ THEN out_no_funds=TRUE; RETURN; END IF; - UPDATE reserves + UPDATE exchange.reserves SET purses_active=purses_active+1 WHERE reserve_pub=in_reserve_pub AND purses_active < purses_allowed; @@ -1901,7 +1901,7 @@ ELSE RETURN; END IF; ELSE - UPDATE reserves + UPDATE exchange.reserves SET current_balance_frac=current_balance_frac-in_purse_fee_frac + CASE @@ -1993,7 +1993,7 @@ THEN RETURN; END IF; -UPDATE purse_requests +UPDATE exchange.purse_requests SET refunded=TRUE, finished=TRUE WHERE purse_pub=my_purse_pub; @@ -2011,7 +2011,7 @@ FOR my_deposit IN FROM exchange.purse_deposits WHERE purse_pub = my_purse_pub LOOP - UPDATE known_coins SET + UPDATE exchange.known_coins SET remaining_frac=remaining_frac+my_deposit.amount_with_fee_frac - CASE WHEN remaining_frac+my_deposit.amount_with_fee_frac >= 100000000 @@ -2071,7 +2071,7 @@ BEGIN out_idempotent=FALSE; -- Update reserve balance. - UPDATE reserves + UPDATE exchange.reserves SET current_balance_frac=current_balance_frac-in_history_fee_frac + CASE @@ -2103,57 +2103,76 @@ BEGIN END $$; -CREATE OR REPLACE FUNCTION exchange_do_close_request( - IN in_reserve_pub BYTEA, - IN in_close_timestamp INT8, +CREATE OR REPLACE FUNCTION exchange_do_reserve_open_deposit( + IN in_coin_pub BYTEA, + IN in_known_coin_id INT8, + IN in_coin_sig BYTEA, IN in_reserve_sig BYTEA, - OUT out_final_balance_val INT8, - OUT out_final_balance_frac INT4, - OUT out_balance_ok BOOLEAN, - OUT out_conflict BOOLEAN) + IN in_reserve_pub BYTEA, + IN in_coin_total_val INT8, + IN in_coin_total_frac INT4, + OUT out_insufficient_funds BOOLEAN) LANGUAGE plpgsql AS $$ BEGIN - SELECT - current_balance_val - ,current_balance_frac - INTO - out_final_balance_val - ,out_final_balance_frac - FROM exchange.reserves - WHERE reserve_pub=in_reserve_pub; - - IF NOT FOUND - THEN - out_final_balance_val=0; - out_final_balance_frac=0; - out_balance_ok = FALSE; - out_conflict = FALSE; - END IF; - - INSERT INTO exchange.close_requests - (reserve_pub - ,close_timestamp - ,reserve_sig - ,close_val - ,close_frac) - VALUES - (in_reserve_pub - ,in_close_timestamp - ,in_reserve_sig - ,out_final_balance_val - ,out_final_balance_frac) +INSERT INTO exchange.reserves_open_deposits + (reserve_sig + ,reserve_pub + ,request_timestamp + ,coin_pub + ,coin_sig + ,contribution_val + ,contribution_frac + ) + VALUES + (in_reserve_sig + ,in_reserve_pub + ,in_request_timestamp + ,in_coin_pub + ,in_coin_sig + ,in_coin_total_val + ,in_coin_total_frac) ON CONFLICT DO NOTHING; - out_conflict = NOT FOUND; - UPDATE reserves SET - current_balance_val=0 - ,current_balance_frac=0 - WHERE reserve_pub=in_reserve_pub; - out_balance_ok = TRUE; +IF NOT FOUND +THEN + -- Idempotent request known, return success. + out_insufficient_funds=FALSE; + RETURN; +END IF; + + +-- Check and update balance of the coin. +UPDATE exchange.known_coins + SET + remaining_frac=remaining_frac-in_coin_total_frac + + CASE + WHEN remaining_frac < in_coin_total_frac + THEN 100000000 + ELSE 0 + END, + remaining_val=remaining_val-in_coin_total_val + - CASE + WHEN remaining_frac < in_coin_total_frac + THEN 1 + ELSE 0 + END + WHERE coin_pub=in_coin_pub + AND ( (remaining_val > in_coin_total_val) OR + ( (remaining_frac >= in_coin_total_frac) AND + (remaining_val >= in_coin_total_val) ) ); + +IF NOT FOUND +THEN + -- Insufficient balance. + out_insufficient_funds=TRUE; + RETURN; +END IF; + +-- Everything fine, return success! +out_insufficient_funds=FALSE; END $$; - COMMIT; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index bde3c8136..0ce8ff473 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4072,6 +4072,7 @@ struct TALER_EXCHANGEDB_Plugin * @param known_coin_id ID of the coin in the known_coins table * @param coin_total amount to be spent of the coin (including deposit fee) * @param reserve_sig signature by the reserve affirming the open operation + * @param reserve_pub public key of the reserve being opened * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false * @return transaction status code, 0 if operation is already in the DB */ @@ -4083,6 +4084,7 @@ struct TALER_EXCHANGEDB_Plugin uint64_t known_coin_id, const struct TALER_Amount *coin_total, const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, bool *insufficient_funds); @@ -5600,8 +5602,8 @@ struct TALER_EXCHANGEDB_Plugin * @param payto_uri where to wire the funds * @param reserve_sig signature affiming that the account is to be closed * @param request_timestamp timestamp of the close request + * @param balance balance at the time of closing * @param closing_fee closing fee to charge - * @param[out] final_balance set to the final balance in the account that will be wired back to the origin account * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -5610,8 +5612,8 @@ struct TALER_EXCHANGEDB_Plugin const char *payto_uri, const struct TALER_ReserveSignatureP *reserve_sig, struct GNUNET_TIME_Timestamp request_timestamp, - const struct TALER_Amount *closing_fee, - struct TALER_Amount *final_balance); + const struct TALER_Amount *balance, + const struct TALER_Amount *closing_fee); /**