diff --git a/contrib/gana b/contrib/gana index 9dee7d6e8..57d96e8e1 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 9dee7d6e8f967fdc58ae224e19ec03989ac35c52 +Subproject commit 57d96e8e123df90c804a821874fc6cb88671ab75 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/Makefile.am b/src/exchange/Makefile.am index 4d7d6d948..b2365eecb 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -161,8 +161,12 @@ 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 \ 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 ffe5c1f5a..39f383afd 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -398,7 +398,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 new file mode 100644 index 000000000..a740bb25a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_attest.c @@ -0,0 +1,378 @@ +/* + 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_attest.c + * @brief Handle /reserves/$RESERVE_PUB/attest requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#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" + + +/** + * 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; + + /** + * Hash of the payto URI of this reserve. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Timestamp of the request. + */ + 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; + + /** + * Attributes we are affirming. + */ + 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; + +}; + + +/** + * 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) +{ + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + enum TALER_ErrorCode ec; + struct GNUNET_TIME_Timestamp now; + + 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, + GNUNET_JSON_pack_data_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_pack_array_steal ("attest", + rhc->json_attest)); +} + + +/** + * 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 + * 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; + + 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, + "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; + } + 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 = { + .etime = GNUNET_TIME_UNIT_FOREVER_TS + }; + MHD_RESULT mhd_ret; + 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 () + }; + 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); + } + + if (GNUNET_OK != + 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, + MHD_HTTP_FORBIDDEN, + 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", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_attest_transaction, + &rsc)) + { + 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; +} + + +/* 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..be36f1e73 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -0,0 +1,419 @@ +/* + 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_kyclogic_lib.h" +#include "taler_mhd_lib.h" +#include "taler_json_lib.h" +#include "taler_dbevents.h" +#include "taler-exchange-httpd_wire.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; + + /** + * 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, 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; +}; + + +/** + * 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) +{ + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + TALER_JSON_pack_amount ("wire_amount", + &rhc->wire_amount)); +} + + +/** + * 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 + * 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 = NULL; + const struct TALER_WireFeeSet *wf; + + qs = TEH_plugin->select_reserve_close_info ( + 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, + "select_reserve_close_info"); + return qs; + 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; + } + + if ( (NULL == rcc->payto_uri) && + (NULL == payto_uri) ) + { + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_RESERVES_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)) ) ) + { + const char *kyc_needed; + + 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, + &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 (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, + &balance, + &wf->closing); + 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_mark_optional ( + 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); + } + } + + 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; + } + 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); +} + + +/* 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.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 new file mode 100644 index 000000000..aa0f26a4a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_get_attest.c @@ -0,0 +1,243 @@ +/* + 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_get_attest.c + * @brief Handle GET /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_kyclogic_lib.h" +#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; + + /** + * 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. + */ + bool not_found; +}; + + +/** + * 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, + * 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; + + 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, + "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; + } + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, + const char *const args[1]) +{ + struct ReserveAttestContext rsc = { + .attributes = NULL + }; + + 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_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; + + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "get-attestable", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &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, + GNUNET_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_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 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..d446d9b40 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -0,0 +1,432 @@ +/* + 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.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_common_deposit.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; + + /** + * 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. + */ + 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 to be paid from the reserve. + */ + struct TALER_Amount reserve_payment; + + /** + * Actual cost to open the reserve. + */ + struct TALER_Amount open_cost; + + /** + * Total amount that was deposited. + */ + struct TALER_Amount total; + + /** + * 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; +}; + + +/** + * 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 *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, + status, + 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); +} + + +/** + * 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 (!) + * @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; + + 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, + rsc->reserve_pub, + &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, + &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: + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do_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; +} + + +MHD_RESULT +TEH_handler_reserves_open (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + struct ReserveOpenContext rsc; + 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 () + }; + + 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 */ + } + } + + { + 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); + rsc.total = rsc.reserve_payment; + 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 */ + } + /* FIXME-DOLD: Alternatively, we could here add coin->amount_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); + } + } + + { + struct TEH_KeyStateHandle *keys; + + keys = TEH_keys_get_state (); + if (NULL == keys) + { + 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, + NULL); + } + rsc.gf = TEH_keys_global_fee_by_time (keys, + rsc.timestamp); + } + 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.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); + } + + { + 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; + } +} + + +/* 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 diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 01ce13dec..1fc89d1b5 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -68,7 +68,13 @@ plugin_LTLIBRARIES = \ endif libtaler_plugin_exchangedb_postgres_la_SOURCES = \ - plugin_exchangedb_postgres.c + 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 libtaler_plugin_exchangedb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \ 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_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c new file mode 100644 index 000000000..bd4f72401 --- /dev/null +++ b/src/exchangedb/pg_do_reserve_open.c @@ -0,0 +1,73 @@ +/* + 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, + struct GNUNET_TIME_Timestamp *final_expiration) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + 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), + 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[] = { + 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, + "do_reserve_open", + "SELECT " + " 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, + "do_reserve_open", + 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..aeef59eb0 --- /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, + 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..43ca944f4 --- /dev/null +++ b/src/exchangedb/pg_insert_close_request.c @@ -0,0 +1,67 @@ +/* + 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 *balance, + const struct TALER_Amount *closing_fee) +{ + 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), + 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 + }; + + PREPARE (pg, + "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 new file mode 100644 index 000000000..c014a10b9 --- /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 balance final balance in the reserve + * @param closing_fee closing fee to charge + * @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 *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 new file mode 100644 index 000000000..8bf70e7b2 --- /dev/null +++ b/src/exchangedb/pg_insert_reserve_open_deposit.c @@ -0,0 +1,66 @@ +/* + 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, + const struct TALER_ReservePublicKeyP *reserve_pub, + 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), + 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 ("out_insufficient_funds", + insufficient_funds), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "insert_reserve_open_deposit", + "SELECT " + " out_insufficient_funds" + " FROM exchange_do_reserve_open_deposit" + " ($1,$2,$3,$4,$5,$6,$7);"); + 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..7eb2fe093 --- /dev/null +++ b/src/exchangedb/pg_insert_reserve_open_deposit.h @@ -0,0 +1,54 @@ +/* + 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 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 + */ +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, + 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 new file mode 100644 index 000000000..772c51e2c --- /dev/null +++ b/src/exchangedb/pg_iterate_kyc_reference.c @@ -0,0 +1,129 @@ +/* + 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" + + +/** + * 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, + 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 + }; + struct IteratorContext ic = { + .cb = lpc, + .cb_cls = lpc_cls, + .pg = pg + }; + + PREPARE (pg, + "iterate_kyc_reference", + "SELECT " + " provider_section" + ",provider_user_id" + ",provider_legitimization_id" + " FROM legitimization_processes" + " 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_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..f1b2d452b --- /dev/null +++ b/src/exchangedb/pg_iterate_reserve_close_info.c @@ -0,0 +1,129 @@ +/* + 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" + +/** + * 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 ( + 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; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_absolute_time (&time_limit), + GNUNET_PQ_query_param_end + }; + struct IteratorContext ic = { + .cb = kac, + .cb_cls = kac_cls, + .pg = pg + }; + + PREPARE (pg, + "iterate_reserve_close_info", + "SELECT" + " amount_val" + ",amount_frac" + ",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_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..0b373b7bb --- /dev/null +++ b/src/exchangedb/pg_select_reserve_close_info.c @@ -0,0 +1,61 @@ +/* + 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_select_reserve_close_info.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; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("close", + balance), + GNUNET_PQ_result_spec_string ("payto_uri", + payto_uri), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "select_reserve_close_info", + "SELECT " + " close_frac" + ",close_val" + ",payto_uri" + " FROM close_requests" + " WHERE reserve_pub=$1;"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "select_reserve_close_info", + 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..d2e2eb5df 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -29,6 +29,12 @@ #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 "pg_iterate_kyc_reference.h" +#include "pg_iterate_reserve_close_info.h" +#include "pg_select_reserve_close_info.h" #include #include #include @@ -42,26 +48,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 +67,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 +4390,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 +4589,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 +16152,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. * @@ -17389,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 - = &postgres_insert_close_request; plugin->insert_drain_profit = &postgres_insert_drain_profit; plugin->profit_drains_get_pending @@ -17419,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_crypto_lib.h b/src/include/taler_crypto_lib.h index b5ae20832..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); @@ -3223,7 +3219,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 +4287,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 diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 261ffb184..0ce8ff473 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. * @@ -4043,6 +4062,98 @@ 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 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 + */ + 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, + const struct TALER_ReservePublicKeyP *reserve_pub, + 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, + 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. * @@ -5488,17 +5599,21 @@ 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[out] final_balance set to the final balance in the account that will be wired back to the origin account + * @param balance balance at the time of closing + * @param closing_fee closing fee to charge * @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, - struct TALER_Amount *final_balance); + const struct TALER_Amount *balance, + const struct TALER_Amount *closing_fee); /** @@ -5725,6 +5840,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 494f7787d..7f4bf5b57 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 }; @@ -301,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 96bf16aa7..0c4a51124 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; @@ -1308,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 */ 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; 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/testing/Makefile.am b/src/testing/Makefile.am index 78d4cdced..256276ce6 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -85,7 +85,9 @@ 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 \ testing_api_cmd_reserve_open.c \ testing_api_cmd_reserve_purse.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, 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, 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,