From c1502e507b0e9b85ad58b0545e1d26143c93f131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Sat, 21 Jan 2023 10:16:20 +0100 Subject: [PATCH 01/10] -fix gana.git url in .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 416977559..f2b9611b2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,4 +7,4 @@ branch = prebuilt [submodule "contrib/gana"] path = contrib/gana - url = https://git.gnunet.org/git/gana.git + url = https://git.gnunet.org/gana.git From 31286b66f253c62ab3c33827490b55a4cc025608 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 21 Jan 2023 21:38:50 +0100 Subject: [PATCH 02/10] -integrate new AML APIs into build --- contrib/gana | 2 +- src/lib/Makefile.am | 3 +++ src/lib/exchange_api_add_aml_decision.c | 7 ++++--- src/lib/exchange_api_management_update_aml_officer.c | 8 ++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/contrib/gana b/contrib/gana index 832685b6a..66228b8a4 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 832685b6a942a6ebbec8e1e5b8c33b6b85b0a727 +Subproject commit 66228b8a4306f028d843d78fbfcca54260539ff9 diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index b775719e6..00b604acf 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -21,6 +21,7 @@ libtalerexchange_la_LDFLAGS = \ -version-info 5:0:0 \ -no-undefined libtalerexchange_la_SOURCES = \ + exchange_api_add_aml_decision.c \ exchange_api_auditor_add_denomination.c \ exchange_api_batch_deposit.c \ exchange_api_batch_withdraw.c \ @@ -37,6 +38,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_kyc_proof.c \ exchange_api_kyc_wallet.c \ exchange_api_link.c \ + exchange_api_management_add_partner.c \ exchange_api_management_auditor_disable.c \ exchange_api_management_auditor_enable.c \ exchange_api_management_drain_profits.c \ @@ -47,6 +49,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_management_revoke_signing_key.c \ exchange_api_management_set_global_fee.c \ exchange_api_management_set_wire_fee.c \ + exchange_api_management_update_aml_officer.c \ exchange_api_management_wire_disable.c \ exchange_api_management_wire_enable.c \ exchange_api_melt.c \ diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c index 7230c5ed4..d4b0d1250 100644 --- a/src/lib/exchange_api_add_aml_decision.c +++ b/src/lib/exchange_api_add_aml_decision.c @@ -136,7 +136,7 @@ TALER_EXCHANGE_add_aml_decision ( TALER_EXCHANGE_AddAmlDecisionCallback cb, void *cb_cls) { - struct TALER_AmlOfficerPrivateKeyP officer_pub; + struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; struct TALER_EXCHANGE_AddAmlDecision *wh; CURL *eh; @@ -146,6 +146,7 @@ TALER_EXCHANGE_add_aml_decision ( &officer_pub.eddsa_pub); TALER_officer_aml_decision_sign (justification, decision_time, + new_threshold, h_payto, new_state, officer_priv, @@ -187,8 +188,8 @@ TALER_EXCHANGE_add_aml_decision ( &officer_sig), GNUNET_JSON_pack_data_auto ("h_payto", h_payto), - GNUNET_JSON_pack_data_uint64 ("state", - (uint32_t) new_state), + GNUNET_JSON_pack_uint64 ("state", + (uint32_t) new_state), TALER_JSON_pack_amount ("new_threshold", new_threshold), GNUNET_JSON_pack_timestamp ("decision_time", diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c index bdc0dbe4a..96ff5a4c7 100644 --- a/src/lib/exchange_api_management_update_aml_officer.c +++ b/src/lib/exchange_api_management_update_aml_officer.c @@ -175,10 +175,10 @@ TALER_EXCHANGE_management_update_aml_officer ( officer_name), GNUNET_JSON_pack_data_auto ("master_sig", master_sig), - GNUNET_JSON_pack_data_bool ("is_active", - is_active), - GNUNET_JSON_pack_data_bool ("read_only", - read_only), + GNUNET_JSON_pack_bool ("is_active", + is_active), + GNUNET_JSON_pack_bool ("read_only", + read_only), GNUNET_JSON_pack_timestamp ("change_date", change_date)); eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); From d131951fbe09b3415c9976acd11f660d51493086 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 21 Jan 2023 23:08:29 +0100 Subject: [PATCH 03/10] add new endpoints to main dispatcher --- contrib/gana | 2 +- src/exchange/Makefile.am | 3 + src/exchange/taler-exchange-httpd.c | 226 +++++++++++++++++- src/exchange/taler-exchange-httpd.h | 4 +- .../taler-exchange-httpd_aml-decision.c | 57 +++-- .../taler-exchange-httpd_aml-decision.h | 45 ++++ .../taler-exchange-httpd_management.h | 26 ++ ...r-exchange-httpd_management_aml-officers.c | 21 +- ...taler-exchange-httpd_management_partners.c | 29 ++- src/exchangedb/0002-partners.sql | 1 + src/exchangedb/pg_insert_aml_decision.c | 12 +- src/exchangedb/pg_insert_aml_decision.h | 9 +- src/exchangedb/pg_insert_aml_officer.c | 8 +- src/exchangedb/pg_insert_aml_officer.h | 9 +- src/exchangedb/pg_insert_partner.c | 19 +- src/include/taler_exchangedb_plugin.h | 13 +- src/lib/exchange_api_add_aml_decision.c | 2 +- src/lib/exchange_api_management_add_partner.c | 26 +- ...change_api_management_update_aml_officer.c | 24 +- 19 files changed, 419 insertions(+), 117 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_aml-decision.h diff --git a/contrib/gana b/contrib/gana index 66228b8a4..7884adf99 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 66228b8a4306f028d843d78fbfcca54260539ff9 +Subproject commit 7884adf99ec4d5ccf52b1a5a251b99fb6ab9c2f6 diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 29596c381..ffcfc5e99 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -123,6 +123,7 @@ taler_exchange_wirewatch_LDADD = \ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd.c taler-exchange-httpd.h \ taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \ + taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \ taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \ taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \ taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \ @@ -139,12 +140,14 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \ taler-exchange-httpd_link.c taler-exchange-httpd_link.h \ taler-exchange-httpd_management.h \ + taler-exchange-httpd_management_aml-officers.c \ taler-exchange-httpd_management_auditors.c \ taler-exchange-httpd_management_auditors_AP_disable.c \ taler-exchange-httpd_management_denominations_HDP_revoke.c \ taler-exchange-httpd_management_drain.c \ taler-exchange-httpd_management_extensions.c \ taler-exchange-httpd_management_global_fees.c \ + taler-exchange-httpd_management_partners.c \ taler-exchange-httpd_management_post_keys.c \ taler-exchange-httpd_management_signkey_EP_revoke.c \ taler-exchange-httpd_management_wire_enable.c \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index def4fd4af..5501687fa 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -30,6 +30,7 @@ #include "taler_kyclogic_lib.h" #include "taler_templating_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_aml-decision.h" #include "taler-exchange-httpd_auditors.h" #include "taler-exchange-httpd_batch-deposit.h" #include "taler-exchange-httpd_batch-withdraw.h" @@ -322,6 +323,187 @@ handle_post_coins (struct TEH_RequestContext *rc, } +/** + * Signature of functions that handle operations + * authorized by AML officers. + * + * @param rc request context + * @param officer_pub the public key of the AML officer + * @param root uploaded JSON data + * @return MHD result code + */ +typedef MHD_RESULT +(*AmlOpPostHandler)(struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const json_t *root); + + +/** + * Handle a "/aml/$OFFICER_PUB/$OP" POST request. Parses the "officer_pub" + * EdDSA key of the officer and demultiplexes based on $OP. + * + * @param rc request context + * @param root uploaded JSON data + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_post_aml (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[2]) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + AmlOpPostHandler handler; + + } h[] = { + { + .op = "decision", + .handler = &TEH_handler_post_aml_decision + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &officer_pub, + sizeof (officer_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc, + &officer_pub, + root); + return r404 (rc->connection, + args[1]); +} + + +/** + * Signature of functions that handle operations + * authorized by AML officers. + * + * @param rc request context + * @param officer_pub the public key of the AML officer + * @param args remaining arguments + * @return MHD result code + */ +typedef MHD_RESULT +(*AmlOpGetHandler)(struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *const args[]); + + +/** + * Handle a "/aml/$OFFICER_PUB/$OP" GET request. Parses the "officer_pub" + * EdDSA key of the officer, checks the authentication signature, and + * demultiplexes based on $OP. + * + * @param rc request context + * @param args array of additional options + * @return MHD result code + */ +static MHD_RESULT +handle_get_aml (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + static const struct + { + /** + * Name of the operation (args[1]) + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + AmlOpGetHandler handler; + + } h[] = { +#if FIXME_AML_GET_DECISIONS_NOT_IMPLEMENTED + { + .op = "decisions", + .handler = &TEH_handler_get_aml_decisions + }, + { + .op = "decision", + .handler = &TEH_handler_get_aml_decision + }, +#endif + { + .op = NULL, + .handler = NULL + }, + }; + + if (NULL == args[0]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + "argument missing"); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &officer_pub, + sizeof (officer_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED, + args[0]); + } + if (NULL == args[1]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, + "AML GET operations must specify an operation identifier"); + } + if (1) // FIXME: check AML officer GET signature! + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID, + NULL); + } + for (unsigned int i = 0; NULL != h[i].op; i++) + if (0 == strcmp (h[i].op, + args[1])) + return h[i].handler (rc, + &officer_pub, + &args[2]); + return r404 (rc->connection, + args[1]); +} + + /** * Signature of functions that handle operations on reserves. * @@ -890,6 +1072,8 @@ handle_post_management (struct TEH_RequestContext *rc, &exchange_pub, root); } + /* FIXME-STYLE: all of the following can likely be nicely combined + into an array-based dispatcher to deduplicate the logic... */ if (0 == strcmp (args[0], "keys")) { @@ -967,6 +1151,30 @@ handle_post_management (struct TEH_RequestContext *rc, return TEH_handler_management_post_drain (rc->connection, root); } + if (0 == strcmp (args[0], + "aml-officers")) + { + if (NULL != args[1]) + { + GNUNET_break_op (0); + return r404 (rc->connection, + "/management/aml-officers/*"); + } + return TEH_handler_management_aml_officers (rc->connection, + root); + } + if (0 == strcmp (args[0], + "partners")) + { + if (NULL != args[1]) + { + GNUNET_break_op (0); + return r404 (rc->connection, + "/management/partners/*"); + } + return TEH_handler_management_partners (rc->connection, + root); + } GNUNET_break_op (0); return r404 (rc->connection, "/management/*"); @@ -1289,6 +1497,22 @@ handle_mhd_request (void *cls, .nargs = 4, .nargs_is_upper_bound = true }, + /* AML endpoints */ + { + .url = "aml", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &handle_get_aml, + .nargs = 4, + .nargs_is_upper_bound = true + }, + { + .url = "aml", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &handle_post_aml, + .nargs = 2 + }, + + /* mark end of list */ { .url = NULL diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index 2be26f14d..7715444a3 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -213,7 +213,7 @@ struct TEH_RequestHandler * * @param rc context for the request * @param json uploaded JSON data - * @param args array of arguments, needs to be of length @e args_expected + * @param args array of arguments, needs to be of length @e nargs * @return MHD result code */ MHD_RESULT @@ -225,7 +225,7 @@ struct TEH_RequestHandler * Function to call to handle DELETE requests. * * @param rc context for the request - * @param args array of arguments, needs to be of length @e args_expected + * @param args array of arguments, needs to be of length @e nargs * @return MHD result code */ MHD_RESULT diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c index c93c10668..4589c8eb9 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision.c +++ b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -31,27 +31,26 @@ MHD_RESULT -TEH_handler_management_post_aml_decision ( - struct MHD_Connection *connection, +TEH_handler_post_aml_decision ( + struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, const json_t *root) { + struct MHD_Connection *connection = rc->connection; const char *justification; struct GNUNET_TIME_Timestamp decision_time; struct TALER_Amount new_threshold; struct TALER_PaytoHashP h_payto; uint32_t new_state32; enum TALER_AmlDecisionState new_state; - struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; struct GNUNET_JSON_Specification spec[] = { - // FIXME: officer_pub is in URL path, not in JSON body! - GNUNET_JSON_spec_fixed_auto ("officer_pub", - &officer_pub), GNUNET_JSON_spec_fixed_auto ("officer_sig", &officer_sig), GNUNET_JSON_spec_fixed_auto ("h_payto", &h_payto), TALER_JSON_spec_amount ("new_threshold", + TEH_currency, &new_threshold), GNUNET_JSON_spec_string ("justification", &justification), @@ -76,13 +75,13 @@ TEH_handler_management_post_aml_decision ( new_state = (enum TALER_AmlDecisionState) new_state32; TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != - TALER_exchange_aml_decision_verify (justification, - decision_time, - &new_threshold, - &h_payto, - new_state, - &officer_pub, - &officer_sig)) + TALER_officer_aml_decision_verify (justification, + decision_time, + &new_threshold, + &h_payto, + new_state, + &officer_pub, + &officer_sig)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( @@ -97,25 +96,25 @@ TEH_handler_management_post_aml_decision ( bool invalid_officer; do { - qs = TEH_plugin->add_aml_decision (TEH_plugin->cls, - justification, - decision_time, - &new_threshold, - &h_payto, - new_state, - &officer_pub, - &officer_sig, - &invalid_officer, - &last_date); + // FIXME: bound loop? + qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls, + &h_payto, + &new_threshold, + new_state, + decision_time, + justification, + &officer_pub, + &officer_sig, + &invalid_officer, + &last_date); } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (qs < 0) { GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "add aml_decision"); - return qs; + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "add aml_decision"); } if (invalid_officer) { @@ -127,7 +126,7 @@ TEH_handler_management_post_aml_decision ( } if (GNUNET_TIME_timestamp_cmp (last_date, >, - validity_start)) + decision_time)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( diff --git a/src/exchange/taler-exchange-httpd_aml-decision.h b/src/exchange/taler-exchange-httpd_aml-decision.h new file mode 100644 index 000000000..e6e355524 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_aml-decision.h @@ -0,0 +1,45 @@ +/* + This file is part of TALER + Copyright (C) 2023 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_aml_decision.h + * @brief Handle /aml/$OFFICER_PUB/decision requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_AML_DECISION_H +#define TALER_EXCHANGE_HTTPD_AML_DECISION_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle an "/aml/$OFFICER_PUB/decision" request. Parses the decision + * details, checks the signatures and if appropriately authorized excecutes + * the decision. + * + * @param rc request context + * @param officer_pub public key of the AML officer who made the request + * @param root uploaded JSON data + * @return MHD result code + */ +MHD_RESULT +TEH_handler_post_aml_decision ( + struct TEH_RequestContext *rc, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const json_t *root); + + +#endif diff --git a/src/exchange/taler-exchange-httpd_management.h b/src/exchange/taler-exchange-httpd_management.h index a5a8b0e72..2fc1fe8db 100644 --- a/src/exchange/taler-exchange-httpd_management.h +++ b/src/exchange/taler-exchange-httpd_management.h @@ -174,6 +174,32 @@ TEH_handler_management_post_drain ( const json_t *root); +/** + * Handle a POST "/management/aml-officers" request. + * + * @param connection the MHD connection to handle + * @param root uploaded JSON data + * @return MHD result code + */ +MHD_RESULT +TEH_handler_management_aml_officers ( + struct MHD_Connection *connection, + const json_t *root); + + +/** + * Handle a POST "/management/partners" request. + * + * @param connection the MHD connection to handle + * @param root uploaded JSON data + * @return MHD result code + */ +MHD_RESULT +TEH_handler_management_partners ( + struct MHD_Connection *connection, + const json_t *root); + + /** * Initialize extension configuration handling. * diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c index 139ccdb25..b82c18d47 100644 --- a/src/exchange/taler-exchange-httpd_management_aml-officers.c +++ b/src/exchange/taler-exchange-httpd_management_aml-officers.c @@ -92,14 +92,15 @@ TEH_handler_management_aml_officers ( struct GNUNET_TIME_Timestamp last_date; do { - qs = TEH_plugin->set_aml_officer (TEH_plugin->cls, - &officer_pub, - officer_name, - change_date, - is_active, - read_only, - &master_sig, - &last_date); + // FIXME: bound loop! + qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls, + &officer_pub, + &master_sig, + officer_name, + is_active, + read_only, + change_date, + &last_date); } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (qs < 0) { @@ -107,13 +108,13 @@ TEH_handler_management_aml_officers ( return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_STORE_FAILED, - "XXX"); + "insert_aml_officer"); } if (GNUNET_TIME_timestamp_cmp (last_date, >, change_date)) { - GNUNER_break_op (0); + GNUNET_break_op (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_CONFLICT, diff --git a/src/exchange/taler-exchange-httpd_management_partners.c b/src/exchange/taler-exchange-httpd_management_partners.c index 5d860120a..1c0d4a9a1 100644 --- a/src/exchange/taler-exchange-httpd_management_partners.c +++ b/src/exchange/taler-exchange-httpd_management_partners.c @@ -51,13 +51,14 @@ TEH_handler_management_partners ( GNUNET_JSON_spec_string ("partner_base_url", &partner_base_url), TALER_JSON_spec_amount ("wad_fee", + TEH_currency, &wad_fee), GNUNET_JSON_spec_timestamp ("start_date", &start_date), GNUNET_JSON_spec_timestamp ("end_date", &start_date), - GNUNET_JSON_spec_time_rel ("wad_frequency", - &wad_frequency), + GNUNET_JSON_spec_relative_time ("wad_frequency", + &wad_frequency), GNUNET_JSON_spec_end () }; @@ -94,14 +95,14 @@ TEH_handler_management_partners ( { enum GNUNET_DB_QueryStatus qs; - qs = TEH_plugin->add_partner (TEH_plugin->cls, - &partner_pub, - start_date, - end_date, - wad_frequency, - &wad_fee, - partner_base_url, - &master_sig); + qs = TEH_plugin->insert_partner (TEH_plugin->cls, + &partner_pub, + start_date, + end_date, + wad_frequency, + &wad_fee, + partner_base_url, + &master_sig); if (qs < 0) { GNUNET_break (0); @@ -110,6 +111,14 @@ TEH_handler_management_partners ( TALER_EC_GENERIC_DB_STORE_FAILED, "add_partner"); } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + /* FIXME: check for idempotency! */ + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_DATA_CONFLICT, + NULL); + } } return TALER_MHD_reply_static ( connection, diff --git a/src/exchangedb/0002-partners.sql b/src/exchangedb/0002-partners.sql index ff57f8fc1..c80f2d745 100644 --- a/src/exchangedb/0002-partners.sql +++ b/src/exchangedb/0002-partners.sql @@ -25,6 +25,7 @@ CREATE TABLE partners ,wad_fee_frac INT4 NOT NULL ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,partner_base_url TEXT NOT NULL + ,PRIMARY KEY (partner_master_pub, start_date) ); COMMENT ON TABLE partners IS 'exchanges we do wad transfers to'; diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c index d046c87f9..421628f65 100644 --- a/src/exchangedb/pg_insert_aml_decision.c +++ b/src/exchangedb/pg_insert_aml_decision.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 @@ -32,10 +32,12 @@ TEH_PG_insert_aml_decision ( const struct TALER_PaytoHashP *h_payto, const struct TALER_Amount *new_threshold, enum TALER_AmlDecisionState new_status, - struct GNUNET_TIME_Absolute decision_time, + struct GNUNET_TIME_Timestamp decision_time, const char *justification, const struct TALER_AmlOfficerPublicKeyP *decider_pub, - const struct TALER_AmlOfficerSignatureP *decider_sig) + const struct TALER_AmlOfficerSignatureP *decider_sig, + bool *invalid_officer, + struct GNUNET_TIME_Timestamp *last_date) { struct PostgresClosure *pg = cls; uint32_t ns = (uint32_t) new_status; @@ -43,13 +45,15 @@ TEH_PG_insert_aml_decision ( GNUNET_PQ_query_param_auto_from_type (h_payto), TALER_PQ_query_param_amount (new_threshold), GNUNET_PQ_query_param_uint32 (&ns), - GNUNET_PQ_query_param_absolute_time (&decision_time), + GNUNET_PQ_query_param_timestamp (&decision_time), GNUNET_PQ_query_param_string (justification), GNUNET_PQ_query_param_auto_from_type (decider_pub), GNUNET_PQ_query_param_auto_from_type (decider_sig), GNUNET_PQ_query_param_end }; + // FIXME: set invalid_officer + // FIXME: set last_date! PREPARE (pg, "insert_aml_decision", "INSERT INTO aml_history " diff --git a/src/exchangedb/pg_insert_aml_decision.h b/src/exchangedb/pg_insert_aml_decision.h index 205c1c74b..94f648fb1 100644 --- a/src/exchangedb/pg_insert_aml_decision.h +++ b/src/exchangedb/pg_insert_aml_decision.h @@ -38,6 +38,9 @@ * @param justification human-readable text justifying the decision * @param decider_pub public key of the staff member * @param decider_sig signature of the staff member + * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now + * @param[out] last_date set to the previous decision time; + * the INSERT is not performed if @a last_date is not before @a decision_time * @return database transaction status */ enum GNUNET_DB_QueryStatus @@ -46,10 +49,12 @@ TEH_PG_insert_aml_decision ( const struct TALER_PaytoHashP *h_payto, const struct TALER_Amount *new_threshold, enum TALER_AmlDecisionState new_status, - struct GNUNET_TIME_Absolute decision_time, + struct GNUNET_TIME_Timestamp decision_time, const char *justification, const struct TALER_AmlOfficerPublicKeyP *decider_pub, - const struct TALER_AmlOfficerSignatureP *decider_sig); + const struct TALER_AmlOfficerSignatureP *decider_sig, + bool *invalid_officer, + struct GNUNET_TIME_Timestamp *last_date); #endif diff --git a/src/exchangedb/pg_insert_aml_officer.c b/src/exchangedb/pg_insert_aml_officer.c index fc2cadef2..33e6c86f7 100644 --- a/src/exchangedb/pg_insert_aml_officer.c +++ b/src/exchangedb/pg_insert_aml_officer.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 @@ -34,7 +34,8 @@ TEH_PG_insert_aml_officer ( const char *decider_name, bool is_active, bool read_only, - struct GNUNET_TIME_Absolute last_change) + struct GNUNET_TIME_Timestamp last_change, + struct GNUNET_TIME_Timestamp *previous_change) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -43,10 +44,11 @@ TEH_PG_insert_aml_officer ( GNUNET_PQ_query_param_string (decider_name), GNUNET_PQ_query_param_bool (is_active), GNUNET_PQ_query_param_bool (read_only), - GNUNET_PQ_query_param_absolute_time (&last_change), + GNUNET_PQ_query_param_timestamp (&last_change), GNUNET_PQ_query_param_end }; + // FIXME: need to check for previous record! PREPARE (pg, "insert_aml_staff", "INSERT INTO aml_staff " diff --git a/src/exchangedb/pg_insert_aml_officer.h b/src/exchangedb/pg_insert_aml_officer.h index be034d9ff..3c6f5d82e 100644 --- a/src/exchangedb/pg_insert_aml_officer.h +++ b/src/exchangedb/pg_insert_aml_officer.h @@ -27,7 +27,10 @@ /** - * Insert AML staff record. + * Insert AML staff record. If the time given in + * @a last_change is before the previous change in the + * database, only @e previous_change is returned and + * no actual change is committed to the database. * * @param cls closure * @param decider_pub public key of the staff member @@ -36,6 +39,7 @@ * @param is_active true to enable, false to set as inactive * @param read_only true to set read-only access * @param last_change when was the change made effective + * @param[out] previous_change set to the time of the previous change * @return database transaction status */ enum GNUNET_DB_QueryStatus @@ -46,6 +50,7 @@ TEH_PG_insert_aml_officer ( const char *decider_name, bool is_active, bool read_only, - struct GNUNET_TIME_Absolute last_change); + struct GNUNET_TIME_Timestamp last_change, + struct GNUNET_TIME_Timestamp *previous_change); #endif diff --git a/src/exchangedb/pg_insert_partner.c b/src/exchangedb/pg_insert_partner.c index 567f37763..5abb2c910 100644 --- a/src/exchangedb/pg_insert_partner.c +++ b/src/exchangedb/pg_insert_partner.c @@ -28,13 +28,13 @@ enum GNUNET_DB_QueryStatus TEH_PG_insert_partner (void *cls, - const struct TALER_MasterPublicKeyP *master_pub, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - struct GNUNET_TIME_Relative wad_frequency, - const struct TALER_Amount *wad_fee, - const char *partner_base_url, - const struct TALER_MasterSignatureP *master_sig) + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterSignatureP *master_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -61,10 +61,9 @@ TEH_PG_insert_partner (void *cls, " ,master_sig" " ,partner_base_url" " ) VALUES " - " ($1, $2, $3, $4, $5, $6, $7, $8);"); + " ($1, $2, $3, $4, $5, $6, $7, $8)" + " ON CONFLICT DO NOTHING;"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_partner", params); } - - diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 146321eab..d6bf798c5 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -6602,6 +6602,7 @@ struct TALER_EXCHANGEDB_Plugin * @param is_active true to enable, false to set as inactive * @param read_only true to set read-only access * @param last_change when was the change made effective + * @param[out] previous_change when was the previous change made * @return database transaction status */ enum GNUNET_DB_QueryStatus @@ -6612,7 +6613,8 @@ struct TALER_EXCHANGEDB_Plugin const char *decider_name, bool is_active, bool read_only, - struct GNUNET_TIME_Absolute last_change); + struct GNUNET_TIME_Timestamp last_change, + struct GNUNET_TIME_Timestamp *previous_change); /** @@ -6727,6 +6729,9 @@ struct TALER_EXCHANGEDB_Plugin * @param justification human-readable text justifying the decision * @param decider_pub public key of the staff member * @param decider_sig signature of the staff member + * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now + * @param[out] last_date set to the previous decision time; + * the INSERT is not performed if @a last_date is not before @a decision_time * @return database transaction status */ enum GNUNET_DB_QueryStatus @@ -6735,10 +6740,12 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_PaytoHashP *h_payto, const struct TALER_Amount *new_threshold, enum TALER_AmlDecisionState new_status, - struct GNUNET_TIME_Absolute decision_time, + struct GNUNET_TIME_Timestamp decision_time, const char *justification, const struct TALER_AmlOfficerPublicKeyP *decider_pub, - const struct TALER_AmlOfficerSignatureP *decider_sig); + const struct TALER_AmlOfficerSignatureP *decider_sig, + bool *invalid_officer, + struct GNUNET_TIME_Timestamp *last_date); }; diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c index d4b0d1250..c6283532b 100644 --- a/src/lib/exchange_api_add_aml_decision.c +++ b/src/lib/exchange_api_add_aml_decision.c @@ -167,7 +167,7 @@ TALER_EXCHANGE_add_aml_decision ( sizeof (opus)); *end = '\0'; GNUNET_asprintf (&path, - "aml-decision/%s", + "aml/%s/decision", opus); wh->url = TALER_url_join (url, path, diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c index 264fd664e..75fb8aa6f 100644 --- a/src/lib/exchange_api_management_add_partner.c +++ b/src/lib/exchange_api_management_add_partner.c @@ -66,7 +66,7 @@ struct TALER_EXCHANGE_ManagementAddPartner /** * Function called when we're done processing the - * HTTP POST /aml-decision/$OFFICER_PUB request. + * HTTP POST /management/partners request. * * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *` * @param response_code HTTP response code, 0 on error @@ -145,25 +145,9 @@ TALER_EXCHANGE_management_add_partner ( wh->cb = cb; wh->cb_cls = cb_cls; wh->ctx = ctx; - { - char *path; - char opus[sizeof (*partner_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - partner_pub, - sizeof (*partner_pub), - opus, - sizeof (opus)); - *end = '\0'; - GNUNET_asprintf (&path, - "management/partners/%s", - opus); - wh->url = TALER_url_join (url, - path, - NULL); - GNUNET_free (path); - } + wh->url = TALER_url_join (url, + "management/partners", + NULL); if (NULL == wh->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -180,6 +164,8 @@ TALER_EXCHANGE_management_add_partner ( end_date), GNUNET_JSON_pack_time_rel ("wad_frequency", wad_frequency), + GNUNET_JSON_pack_data_auto ("partner_pub", + &partner_pub), GNUNET_JSON_pack_data_auto ("master_sig", &master_sig), TALER_JSON_pack_amount ("wad_fee", diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c index 96ff5a4c7..6e1669465 100644 --- a/src/lib/exchange_api_management_update_aml_officer.c +++ b/src/lib/exchange_api_management_update_aml_officer.c @@ -144,25 +144,9 @@ TALER_EXCHANGE_management_update_aml_officer ( wh->cb = cb; wh->cb_cls = cb_cls; wh->ctx = ctx; - { - char *path; - char opus[sizeof (*officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - officer_pub, - sizeof (*officer_pub), - opus, - sizeof (opus)); - *end = '\0'; - GNUNET_asprintf (&path, - "management/aml-officers/%s", - opus); - wh->url = TALER_url_join (url, - path, - NULL); - GNUNET_free (path); - } + wh->url = TALER_url_join (url, + "management/aml-officers", + NULL); if (NULL == wh->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -173,6 +157,8 @@ TALER_EXCHANGE_management_update_aml_officer ( body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("officer_name", officer_name), + GNUNET_JSON_pack_data_auto ("officer_pub", + officer_pub), GNUNET_JSON_pack_data_auto ("master_sig", master_sig), GNUNET_JSON_pack_bool ("is_active", From f8bfc4dc9d9dcb69ccdd95258aea53dabf997246 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Jan 2023 15:13:34 +0100 Subject: [PATCH 04/10] address DB FIXMEs for AML --- src/exchangedb/0003-aml_history.sql | 2 +- .../exchange_do_insert_aml_decision.sql | 102 ++++++++++++++++++ .../exchange_do_insert_aml_officer.sql | 74 +++++++++++++ src/exchangedb/pg_insert_aml_decision.c | 32 +++--- src/exchangedb/pg_insert_aml_decision.h | 2 +- src/exchangedb/pg_insert_aml_officer.c | 26 ++--- src/exchangedb/pg_persist_policy_details.c | 10 +- src/exchangedb/procedures.sql.in | 2 + 8 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 src/exchangedb/exchange_do_insert_aml_decision.sql create mode 100644 src/exchangedb/exchange_do_insert_aml_officer.sql diff --git a/src/exchangedb/0003-aml_history.sql b/src/exchangedb/0003-aml_history.sql index c2ab532da..1c737265b 100644 --- a/src/exchangedb/0003-aml_history.sql +++ b/src/exchangedb/0003-aml_history.sql @@ -110,7 +110,7 @@ BEGIN EXECUTE FORMAT ( 'CREATE INDEX ' || table_name || '_main_index ' 'ON ' || table_name || ' ' - '(h_payto ASC, decision_time ASC);' + '(h_payto, decision_time DESC);' ); END $$; diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql new file mode 100644 index 000000000..480736afa --- /dev/null +++ b/src/exchangedb/exchange_do_insert_aml_decision.sql @@ -0,0 +1,102 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2023 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 +-- + +CREATE OR REPLACE FUNCTION exchange_do_insert_aml_decision( + IN in_h_payto BYTEA, + IN in_new_threshold_val INT8, + IN in_new_threshold_frac INT4, + IN in_new_status INT4, + IN in_decision_time INT8, + IN in_justification VARCHAR, + IN in_decider_pub BYTEA, + IN in_decider_sig BYTEA, + OUT out_invalid_officer BOOLEAN, + OUT out_last_date INT8) +LANGUAGE plpgsql +AS $$ + +-- Check officer is eligible to make decisions. +PERFORM + FROM exchange.aml_staff + WHERE decider_pub=in_decider_pub + AND is_active + AND NOT read_only; +IF NOT FOUND +THEN + out_invalid_officer=TRUE; + out_last_date=0; + RETURN; +END IF; +out_invalid_officer=FALSE; + +-- Check no more recent decision exists. +SELECT decision_time + INTO out_last_date + FROM exchange.aml_history + WHERE h_payto=in_h_payto + ORDER BY decision_time DESC; +IF FOUND +THEN + IF out_last_date >= in_decision_time + THEN + -- Refuse to insert older decision. + RETURN; + END IF; + UPDATE exchange.aml_status + SET threshold_val=in_threshold_val + ,threshold_frac=in_threshold_frac + ,status=in_new_status + WHERE h_payto=in_h_payto; + ASSERT FOUND, 'cannot have AML decision history but no AML status'; +ELSE + out_last_date = 0; + INSERT INTO exchange.aml_status + (h_payto + ,threshold_val + ,threshold_frac + ,status) + VALUES + (in_h_payto + ,in_threshold_val + ,in_threshold_frac + ,in_new_status); +END IF; + + +INSERT INTO exchange.aml_history + (h_payto + ,new_threshold_val + ,new_threshold_frac + ,new_status + ,decision_time + ,justification + ,decider_pub + ,decider_sig + ) VALUES + (in_h_payto + ,in_new_threshold_val + ,in_new_threshold_frac + ,in_new_status + ,in_decision_time + ,in_justification + ,in_decider_pub + ,in_decider_sig); + +END $$; + + +COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, INT8, INT4, INT4, INT8, VARCHAR, BYTEA, BYTEA) + IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table'; diff --git a/src/exchangedb/exchange_do_insert_aml_officer.sql b/src/exchangedb/exchange_do_insert_aml_officer.sql new file mode 100644 index 000000000..72f813e89 --- /dev/null +++ b/src/exchangedb/exchange_do_insert_aml_officer.sql @@ -0,0 +1,74 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2023 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 +-- + +CREATE OR REPLACE FUNCTION exchange_do_insert_aml_officer( + IN in_decider_pub BYTEA, + IN in_master_sig BYTEA, + IN in_decider_name VARCHAR, + IN in_is_active BOOLEAN, + IN in_read_only BOOLEAN, + IN in_last_change INT8, + OUT out_last_change INT8) +LANGUAGE plpgsql +AS $$ + +INSERT INTO exchange.aml_staff + (decider_pub + ,master_sig + ,decider_name + ,is_active + ,read_only + ,last_change + ) VALUES + (in_decider_pub + ,in_master_sig + ,in_decider_name + ,in_is_active + ,in_read_only + ,in_last_change) + ON CONFLICT DO NOTHING; +IF FOUND +THEN + out_last_change=0; + RETURN; +END IF; + +-- Check update is most recent... +SELECT last_change + INTO out_last_change + FROM exchange.aml_staff + WHERE decider_pub=in_decider_pub; +ASSERT FOUND, 'cannot have INSERT conflict but no AML staff record'; + +IF out_last_change >= in_last_change +THEN + -- Refuse to insert older status + RETURN; +END IF; + +-- We are more recent, update existing record. +UPDATE exchange.aml_staff + SET master_sig=in_master_sig + ,decider_name=in_decider_name + ,is_active=in_is_active + ,read_only=in_read_only + ,last_change=in_last_change + WHERE decider_pub=in_decider_pub; +END $$; + + +COMMENT ON FUNCTION exchange_do_insert_aml_officer(BYTEA, BYTEA, VARCHAR, BOOL, BOOL, INT8) + IS 'Inserts or updates AML staff record, making sure the update is more recent than the previous change'; diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c index 421628f65..85570ed85 100644 --- a/src/exchangedb/pg_insert_aml_decision.c +++ b/src/exchangedb/pg_insert_aml_decision.c @@ -51,23 +51,23 @@ TEH_PG_insert_aml_decision ( GNUNET_PQ_query_param_auto_from_type (decider_sig), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("out_invalid_officer", + invalid_officer), + GNUNET_PQ_result_spec_timestamp ("out_last_date", + last_date), + GNUNET_PQ_result_spec_end + }; - // FIXME: set invalid_officer - // FIXME: set last_date! PREPARE (pg, - "insert_aml_decision", - "INSERT INTO aml_history " - "(h_payto" - ",new_threshold_val" - ",new_threshold_frac" - ",new_status" - ",decision_time" - ",justification" - ",decider_pub" - ",decider_sig" - ") VALUES " + "do_insert_aml_decision", + "SELECT" + " out_invalid_officer" + ",out_last_date" + " FROM exchange_do_insert_aml_decision" "($1, $2, $3, $4, $5, $6, $7, $8);"); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_aml_decision", - params); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "do_insert_aml_decision", + params, + rs); } diff --git a/src/exchangedb/pg_insert_aml_decision.h b/src/exchangedb/pg_insert_aml_decision.h index 94f648fb1..b539945a7 100644 --- a/src/exchangedb/pg_insert_aml_decision.h +++ b/src/exchangedb/pg_insert_aml_decision.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 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 diff --git a/src/exchangedb/pg_insert_aml_officer.c b/src/exchangedb/pg_insert_aml_officer.c index 33e6c86f7..c1f635a64 100644 --- a/src/exchangedb/pg_insert_aml_officer.c +++ b/src/exchangedb/pg_insert_aml_officer.c @@ -47,20 +47,20 @@ TEH_PG_insert_aml_officer ( GNUNET_PQ_query_param_timestamp (&last_change), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_timestamp ("out_last_change", + previous_change), + GNUNET_PQ_result_spec_end + }; - // FIXME: need to check for previous record! PREPARE (pg, - "insert_aml_staff", - "INSERT INTO aml_staff " - "(decider_pub" - ",master_sig" - ",decider_name" - ",is_active" - ",read_only" - ",last_change" - ") VALUES " + "do_insert_aml_staff", + "SELECT" + " out_last_change" + " FROM exchange_do_insert_aml_officer" "($1, $2, $3, $4, $5, $6);"); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_aml_staff", - params); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "do_insert_aml_staff", + params, + rs); } diff --git a/src/exchangedb/pg_persist_policy_details.c b/src/exchangedb/pg_persist_policy_details.c index 2b7787878..3bc7afa98 100644 --- a/src/exchangedb/pg_persist_policy_details.c +++ b/src/exchangedb/pg_persist_policy_details.c @@ -59,7 +59,15 @@ TEH_PG_persist_policy_details ( GNUNET_PQ_result_spec_end }; - // FIXME: prepare missing!!?! + PREPARE (pg, + "call_insert_or_update_policy_details", + "SELECT" + " out_policy_details_serial_id AS policy_details_serial_id" + ",out_accumulated_total_val AS accumulated_total_val" + ",out_accumulated_total_frac AS accumulated_total_frac" + ",out_fulfillment_state AS fulfillment_state" + " FROM exchange_do_insert_or_update_policy_details" + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "call_insert_or_update_policy_details", params, diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in index 194830248..73b75af29 100644 --- a/src/exchangedb/procedures.sql.in +++ b/src/exchangedb/procedures.sql.in @@ -37,6 +37,8 @@ SET search_path TO exchange; #include "exchange_do_reserve_open_deposit.sql" #include "exchange_do_reserve_open.sql" #include "exchange_do_insert_or_update_policy_details.sql" +#include "exchange_do_insert_aml_decision.sql" +#include "exchange_do_insert_aml_officer.sql" #include "exchange_do_batch_reserves_in_insert.sql" #include "exchange_do_batch_reserves_update.sql" #include "exchange_do_batch2_reserves_in_insert.sql" From f221db1c035cf9e596300e79fe928c9edc5af2d0 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Jan 2023 15:24:45 +0100 Subject: [PATCH 05/10] -address FIXMEs --- src/exchange/taler-exchange-httpd_aml-decision.c | 14 +++++++++++--- .../taler-exchange-httpd_management_aml-officers.c | 10 +++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c index 4589c8eb9..ae2667c1d 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision.c +++ b/src/exchange/taler-exchange-httpd_aml-decision.c @@ -30,6 +30,12 @@ #include "taler-exchange-httpd_responses.h" +/** + * How often do we try the DB operation at most? + */ +#define MAX_RETRIES 10 + + MHD_RESULT TEH_handler_post_aml_decision ( struct TEH_RequestContext *rc, @@ -80,7 +86,7 @@ TEH_handler_post_aml_decision ( &new_threshold, &h_payto, new_state, - &officer_pub, + officer_pub, &officer_sig)) { GNUNET_break_op (0); @@ -94,19 +100,21 @@ TEH_handler_post_aml_decision ( enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Timestamp last_date; bool invalid_officer; + unsigned int retries_left = MAX_RETRIES; do { - // FIXME: bound loop? qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls, &h_payto, &new_threshold, new_state, decision_time, justification, - &officer_pub, + officer_pub, &officer_sig, &invalid_officer, &last_date); + if (0 == --retries_left) + break; } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (qs < 0) { diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c index b82c18d47..abc7c3d84 100644 --- a/src/exchange/taler-exchange-httpd_management_aml-officers.c +++ b/src/exchange/taler-exchange-httpd_management_aml-officers.c @@ -31,6 +31,12 @@ #include "taler-exchange-httpd_responses.h" +/** + * How often do we try the DB operation at most? + */ +#define MAX_RETRIES 10 + + MHD_RESULT TEH_handler_management_aml_officers ( struct MHD_Connection *connection, @@ -90,9 +96,9 @@ TEH_handler_management_aml_officers ( { enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Timestamp last_date; + unsigned int retries_left = MAX_RETRIES; do { - // FIXME: bound loop! qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls, &officer_pub, &master_sig, @@ -101,6 +107,8 @@ TEH_handler_management_aml_officers ( read_only, change_date, &last_date); + if (0 == --retries_left) + break; } while (GNUNET_DB_STATUS_SOFT_ERROR == qs); if (qs < 0) { From d53dd753e147420c4553724de5a1b653b19dfda2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Jan 2023 15:41:08 +0100 Subject: [PATCH 06/10] -typos --- src/exchange/taler-exchange-httpd_aml-decision.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_aml-decision.h b/src/exchange/taler-exchange-httpd_aml-decision.h index e6e355524..8dd3bb3fc 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision.h +++ b/src/exchange/taler-exchange-httpd_aml-decision.h @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see */ /** - * @file taler-exchange-httpd_aml_decision.h + * @file taler-exchange-httpd_aml-decision.h * @brief Handle /aml/$OFFICER_PUB/decision requests * @author Christian Grothoff */ @@ -27,7 +27,7 @@ /** * Handle an "/aml/$OFFICER_PUB/decision" request. Parses the decision - * details, checks the signatures and if appropriately authorized excecutes + * details, checks the signatures and if appropriately authorized executes * the decision. * * @param rc request context From c2eee251c2eda2ce544a808d77a9a5615a2e7c34 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Jan 2023 19:40:47 +0100 Subject: [PATCH 07/10] add new subcommands for AML staff management and partner exchanges --- src/exchange-tools/taler-exchange-offline.c | 617 +++++++++++++++++++- src/include/taler_exchange_service.h | 2 +- 2 files changed, 617 insertions(+), 2 deletions(-) diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index eb64a6c9d..660864b77 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020, 2021, 2022 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -112,6 +112,16 @@ */ #define OP_DRAIN_PROFITS "exchange-drain-profits-0" +/** + * Setup AML staff. + */ +#define OP_UPDATE_AML_STAFF "exchange-add-aml-staff-0" + +/** + * Setup partner exchange for wad transfers. + */ +#define OP_ADD_PARTNER "exchange-add-partner-0" + /** * Our private key, initialized in #load_offline_key(). */ @@ -498,6 +508,62 @@ struct UploadExtensionsRequest }; +/** + * Data structure for AML staff requests. + */ +struct AmlStaffRequest +{ + + /** + * Kept in a DLL. + */ + struct AmlStaffRequest *next; + + /** + * Kept in a DLL. + */ + struct AmlStaffRequest *prev; + + /** + * Operation handle. + */ + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *h; + + /** + * Array index of the associated command. + */ + size_t idx; +}; + + +/** + * Data structure for partner add requests. + */ +struct PartnerAddRequest +{ + + /** + * Kept in a DLL. + */ + struct PartnerAddRequest *next; + + /** + * Kept in a DLL. + */ + struct PartnerAddRequest *prev; + + /** + * Operation handle. + */ + struct TALER_EXCHANGE_ManagementAddPartner *h; + + /** + * Array index of the associated command. + */ + size_t idx; +}; + + /** * Next work item to perform. */ @@ -508,6 +574,27 @@ static struct GNUNET_SCHEDULER_Task *nxt; */ static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh; + +/** + * Active AML staff change requests. + */ +static struct AmlStaffRequest *asr_head; + +/** + * Active AML staff change requests. + */ +static struct AmlStaffRequest *asr_tail; + +/** + * Active partner add requests. + */ +static struct PartnerAddRequest *par_head; + +/** + * Active partner add requests. + */ +static struct PartnerAddRequest *par_tail; + /** * Active denomiantion revocation requests. */ @@ -629,6 +716,36 @@ do_shutdown (void *cls) { (void) cls; + { + struct AmlStaffRequest *asr; + + while (NULL != (asr = asr_head)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Aborting incomplete AML staff update #%u\n", + (unsigned int) asr->idx); + TALER_EXCHANGE_management_update_aml_officer_cancel (asr->h); + GNUNET_CONTAINER_DLL_remove (asr_head, + asr_tail, + asr); + GNUNET_free (asr); + } + } + { + struct PartnerAddRequest *par; + + while (NULL != (par = par_head)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Aborting incomplete partner add request #%u\n", + (unsigned int) par->idx); + TALER_EXCHANGE_management_add_partner_cancel (par->h); + GNUNET_CONTAINER_DLL_remove (par_head, + par_tail, + par); + GNUNET_free (par); + } + } { struct DenomRevocationRequest *drr; @@ -842,6 +959,8 @@ static void test_shutdown (void) { if ( (NULL == drr_head) && + (NULL == par_head) && + (NULL == asr_head) && (NULL == srr_head) && (NULL == aar_head) && (NULL == adr_head) && @@ -2214,6 +2333,221 @@ upload_extensions (const char *exchange_url, } +/** + * Function called with information about the add partner operation. + * + * @param cls closure with a `struct PartnerAddRequest` + * @param hr HTTP response data + */ +static void +add_partner_cb ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct PartnerAddRequest *par = cls; + + if (MHD_HTTP_NO_CONTENT != hr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Upload failed for command %u with status %u: %s (%s)\n", + (unsigned int) par->idx, + hr->http_status, + TALER_ErrorCode_get_hint (hr->ec), + hr->hint); + global_ret = EXIT_FAILURE; + } + GNUNET_CONTAINER_DLL_remove (par_head, + par_tail, + par); + GNUNET_free (par); + test_shutdown (); +} + + +/** + * Add partner action. + * + * @param exchange_url base URL of the exchange + * @param idx index of the operation we are performing (for logging) + * @param value arguments for add partner + */ +static void +add_partner (const char *exchange_url, + size_t idx, + const json_t *value) +{ + struct TALER_MasterPublicKeyP partner_pub; + struct GNUNET_TIME_Timestamp start_date; + struct GNUNET_TIME_Timestamp end_date; + struct GNUNET_TIME_Relative wad_frequency; + struct TALER_Amount wad_fee; + const char *partner_base_url; + struct TALER_MasterSignatureP master_sig; + struct PartnerAddRequest *par; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("partner_pub", + &partner_pub), + TALER_JSON_spec_amount ("wad_fee", + currency, + &wad_fee), + GNUNET_JSON_spec_relative_time ("wad_frequency", + &wad_frequency), + GNUNET_JSON_spec_timestamp ("start_date", + &start_date), + GNUNET_JSON_spec_timestamp ("end_date", + &end_date), + GNUNET_JSON_spec_string ("partner_base_url", + &partner_base_url), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid input to add partner: %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) idx); + json_dumpf (value, + stderr, + JSON_INDENT (2)); + global_ret = EXIT_FAILURE; + test_shutdown (); + return; + } + par = GNUNET_new (struct PartnerAddRequest); + par->idx = idx; + par->h = + TALER_EXCHANGE_management_add_partner (ctx, + exchange_url, + &partner_pub, + start_date, + end_date, + wad_frequency, + &wad_fee, + partner_base_url, + &master_sig, + &add_partner_cb, + par); + GNUNET_CONTAINER_DLL_insert (par_head, + par_tail, + par); +} + + +/** + * Function called with information about the AML officer update operation. + * + * @param cls closure with a `struct AmlStaffRequest` + * @param hr HTTP response data + */ +static void +update_aml_officer_cb ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct AmlStaffRequest *asr = cls; + + if (MHD_HTTP_NO_CONTENT != hr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Upload failed for command %u with status %u: %s (%s)\n", + (unsigned int) asr->idx, + hr->http_status, + TALER_ErrorCode_get_hint (hr->ec), + hr->hint); + global_ret = EXIT_FAILURE; + } + GNUNET_CONTAINER_DLL_remove (asr_head, + asr_tail, + asr); + GNUNET_free (asr); + test_shutdown (); +} + + +/** + * Upload AML staff action. + * + * @param exchange_url base URL of the exchange + * @param idx index of the operation we are performing (for logging) + * @param value arguments for AML staff change + */ +static void +update_aml_staff (const char *exchange_url, + size_t idx, + const json_t *value) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + const char *officer_name; + struct GNUNET_TIME_Timestamp change_date; + bool is_active; + bool read_only; + struct TALER_MasterSignatureP master_sig; + struct AmlStaffRequest *asr; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("officer_pub", + &officer_pub), + GNUNET_JSON_spec_timestamp ("change_date", + &change_date), + GNUNET_JSON_spec_bool ("is_active", + &is_active), + GNUNET_JSON_spec_bool ("read_only", + &read_only), + GNUNET_JSON_spec_string ("officer_name", + &officer_name), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid input to AML staff update: %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) idx); + json_dumpf (value, + stderr, + JSON_INDENT (2)); + global_ret = EXIT_FAILURE; + test_shutdown (); + return; + } + asr = GNUNET_new (struct AmlStaffRequest); + asr->idx = idx; + asr->h = + TALER_EXCHANGE_management_update_aml_officer (ctx, + exchange_url, + &officer_pub, + officer_name, + change_date, + is_active, + read_only, + &master_sig, + &update_aml_officer_cb, + asr); + GNUNET_CONTAINER_DLL_insert (asr_head, + asr_tail, + asr); +} + + /** * Perform uploads based on the JSON in #out. * @@ -2267,6 +2601,14 @@ trigger_upload (const char *exchange_url) .key = OP_EXTENSIONS, .cb = &upload_extensions }, + { + .key = OP_UPDATE_AML_STAFF, + .cb = &update_aml_staff + }, + { + .key = OP_ADD_PARTNER, + .cb = &add_partner + }, /* array termination */ { .key = NULL @@ -3040,6 +3382,261 @@ do_drain (char *const *args) } +/** + * Add partner. + * + * @param args the array of command-line arguments to process next; + * args[0] must be the partner's master public key, args[1] the partner's + * API base URL, args[2] the wad fee, args[3] the wad frequency, and + * args[4] the year (including possibly 'now') + */ +static void +do_add_partner (char *const *args) +{ + struct TALER_MasterPublicKeyP partner_pub; + struct GNUNET_TIME_Timestamp start_date; + struct GNUNET_TIME_Timestamp end_date; + struct GNUNET_TIME_Relative wad_frequency; + struct TALER_Amount wad_fee; + const char *partner_base_url; + struct TALER_MasterSignatureP master_sig; + char dummy; + unsigned int year; + + if (NULL != in) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Downloaded data was not consumed, not adding partner\n"); + test_shutdown (); + global_ret = EXIT_FAILURE; + return; + } + if ( (NULL == args[0]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &partner_pub, + sizeof (partner_pub))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "You must specify the partner master public key as first argument for this subcommand\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if ( (NULL == args[1]) || + (0 != strncmp ("http", + args[1], + strlen ("http"))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "You must specify the partner's base URL as the 2nd argument to this subcommand\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + partner_base_url = args[1]; + if ( (NULL == args[2]) || + (GNUNET_OK != + TALER_string_to_amount (args[2], + &wad_fee)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid amount `%s' specified for wad fee of partner\n", + args[2]); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if ( (NULL == args[3]) || + (GNUNET_OK != + GNUNET_STRINGS_fancy_time_to_relative (args[3], + &wad_frequency)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid wad frequency `%s' specified for add partner\n", + args[3]); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if ( (NULL == args[4]) || + ( (1 != sscanf (args[4], + "%u%c", + &year, + &dummy)) && + (0 != strcasecmp ("now", + args[4])) ) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid year `%s' specified for add partner\n", + args[4]); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if (0 == strcasecmp ("now", + args[4])) + year = GNUNET_TIME_get_current_year (); + start_date = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_year_to_time (year)); + end_date = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_year_to_time (year + 1)); + + if (GNUNET_OK != + load_offline_key (GNUNET_NO)) + return; + TALER_exchange_offline_partner_details_sign (&partner_pub, + start_date, + end_date, + wad_frequency, + &wad_fee, + partner_base_url, + &master_priv, + &master_sig); + output_operation (OP_ADD_PARTNER, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("partner_base_url", + partner_base_url), + GNUNET_JSON_pack_time_rel ("wad_frequency", + wad_frequency), + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + GNUNET_JSON_pack_data_auto ("partner_pub", + &partner_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + &master_sig))); + next (args + 5); +} + + +/** + * Enable or disable AML staff. + * + * @param is_active true to enable, false to disable + * @param args the array of command-line arguments to process next; args[0] must be the AML staff's public key, args[1] the AML staff's legal name, and if @a is_active then args[2] rw (read write) or ro (read only) + */ +static void +do_set_aml_staff (bool is_active, + char *const *args) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + const char *officer_name; + bool read_only; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); + + if (NULL != in) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Downloaded data was not consumed, not updating AML staff status\n"); + test_shutdown (); + global_ret = EXIT_FAILURE; + return; + } + if ( (NULL == args[0]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &officer_pub, + sizeof (officer_pub))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "You must specify the AML officer's public key as first argument for this subcommand\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + if (NULL == args[1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "You must specify the officer's legal name as the 2nd argument to this subcommand\n"); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + officer_name = args[1]; + if (is_active) + { + if ( (NULL == args[2]) || + ( (0 != strcmp (args[2], + "ro")) && + (0 != strcmp (args[2], + "rw")) ) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "You must specify 'ro' or 'rw' (and not `%s') for the access level\n", + args[2]); + test_shutdown (); + global_ret = EXIT_INVALIDARGUMENT; + return; + } + read_only = (0 == strcmp (args[2], + "ro")); + } + else + { + read_only = true; + } + if (GNUNET_OK != + load_offline_key (GNUNET_NO)) + return; + TALER_exchange_offline_aml_officer_status_sign (&officer_pub, + officer_name, + now, + is_active, + read_only, + &master_priv, + &master_sig); + output_operation (OP_UPDATE_AML_STAFF, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("officer_name", + officer_name), + GNUNET_JSON_pack_timestamp ("change_date", + now), + GNUNET_JSON_pack_bool ("is_active", + is_active), + GNUNET_JSON_pack_bool ("read_only", + read_only), + GNUNET_JSON_pack_data_auto ("officer_pub", + &officer_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + &master_sig))); + next (args + (is_active ? 3 : 2)); +} + + +/** + * Disable AML staff. + * + * @param args the array of command-line arguments to process next; + * args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only) + */ +static void +disable_aml_staff (char *const *args) +{ + do_set_aml_staff (false, + args); +} + + +/** + * Enable AML staff. + * + * @param args the array of command-line arguments to process next; + * args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only) + */ +static void +enable_aml_staff (char *const *args) +{ + do_set_aml_staff (true, + args); +} + + /** * Function called with information about future keys. Dumps the JSON output * (on success), either into an internal buffer or to stdout (depending on @@ -4475,6 +5072,24 @@ work (void *cls) "drain profits from exchange escrow account to regular exchange operator account (amount, debit account configuration section and credit account payto://-URI must be given as arguments)", .cb = &do_drain }, + { + .name = "add-partner", + .help = + "add partner exchange for P2P wad transfers (partner master public key, partner base URL, wad fee, wad frequency and validity year must be given as arguments)", + .cb = &do_add_partner + }, + { + .name = "aml-enable", + .help = + "enable AML staff member (staff member public key, legal name and rw (read write) or ro (read only) must be given as arguments)", + .cb = &enable_aml_staff + }, + { + .name = "aml-disable", + .help = + "disable AML staff member (staff member public key and legal name must be given as arguments)", + .cb = &disable_aml_staff + }, { .name = "upload", .help = diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index ff1698cca..bc98e5329 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -4410,7 +4410,7 @@ TALER_EXCHANGE_management_add_partner ( /** - * Cancel #TALER_EXCHANGE_management_update_aml_officer() operation. + * Cancel #TALER_EXCHANGE_management_add_partner() operation. * * @param rh handle of the operation to cancel */ From a273b176da448cd27374acb94feee22c22dd8527 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Jan 2023 21:51:46 +0100 Subject: [PATCH 08/10] -towards storing KYC attribute data --- src/exchange/taler-exchange-httpd_kyc-proof.c | 3 + src/include/taler_kyclogic_plugin.h | 2 + src/kyclogic/Makefile.am | 1 + src/kyclogic/kyclogic-oauth2.conf | 8 ++ src/kyclogic/plugin_kyclogic_kycaid.c | 1 + src/kyclogic/plugin_kyclogic_oauth2.c | 87 +++++++++++++++++++ src/kyclogic/plugin_kyclogic_persona.c | 2 + src/kyclogic/taler-exchange-kyc-tester.c | 6 ++ 8 files changed, 110 insertions(+) diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index d37164987..1904c4acb 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -169,6 +169,7 @@ TEH_kyc_proof_cleanup (void) * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown * @param expiration until when is the KYC check valid + * @param attributes user attributes returned by the provider * @param http_status HTTP status code of @a response * @param[in] response to return to the HTTP client */ @@ -179,6 +180,7 @@ proof_cb ( const char *provider_user_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration, + const json_t *attributes, unsigned int http_status, struct MHD_Response *response) { @@ -194,6 +196,7 @@ proof_cb ( { enum GNUNET_DB_QueryStatus qs; + // FIXME: also store 'attributes' in DB! qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls, kpc->process_row, kpc->provider_section, diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h index c2266e1ae..1782af917 100644 --- a/src/include/taler_kyclogic_plugin.h +++ b/src/include/taler_kyclogic_plugin.h @@ -158,6 +158,7 @@ typedef void * @param status KYC status * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown + * @param attributes user attributes returned by the provider * @param expiration until when is the KYC check valid * @param http_status HTTP status code of @a response * @param[in] response to return to the HTTP client @@ -169,6 +170,7 @@ typedef void const char *provider_user_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration, + const json_t *attributes, unsigned int http_status, struct MHD_Response *response); diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am index c45424178..858331f39 100644 --- a/src/kyclogic/Makefile.am +++ b/src/kyclogic/Makefile.am @@ -79,6 +79,7 @@ libtaler_plugin_kyclogic_oauth2_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_kyclogic_oauth2_la_LDFLAGS = \ $(TALER_PLUGIN_LDFLAGS) \ + $(top_builddir)/src/templating/libtalertemplating.la \ $(top_builddir)/src/mhd/libtalermhd.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ diff --git a/src/kyclogic/kyclogic-oauth2.conf b/src/kyclogic/kyclogic-oauth2.conf index 7ccf81c0d..d3df585d8 100644 --- a/src/kyclogic/kyclogic-oauth2.conf +++ b/src/kyclogic/kyclogic-oauth2.conf @@ -25,3 +25,11 @@ KYC_OAUTH2_POST_URL = http://example.com/thank-you # For authentication to the OAuth2.0 service KYC_OAUTH2_CLIENT_ID = testcase KYC_OAUTH2_CLIENT_SECRET = password + +# Mustach template that converts OAuth2.0 data about the user +# into GNU Taler standardized attribute data. +# +# This is just an example, details will depend on the +# provider! +# +KYC_ATTRIBUTE_TEMPLATE = "{"fullname":"{{first_name}} {{last_name}}","phone":"{{phone}}"}" \ No newline at end of file diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c index 8e9323171..6926135c3 100644 --- a/src/kyclogic/plugin_kyclogic_kycaid.c +++ b/src/kyclogic/plugin_kyclogic_kycaid.c @@ -632,6 +632,7 @@ proof_reply (void *cls) NULL, /* user id */ NULL, /* provider legi ID */ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ + NULL, /* attributes */ MHD_HTTP_BAD_REQUEST, resp); } diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index 5709b18f8..f5a08e92e 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -21,6 +21,7 @@ #include "platform.h" #include "taler_kyclogic_plugin.h" #include "taler_mhd_lib.h" +#include "taler_templating_lib.h" #include "taler_json_lib.h" #include #include "taler_util.h" @@ -105,6 +106,12 @@ struct TALER_KYCLOGIC_ProviderDetails */ char *post_kyc_redirect_url; + /** + * Template for converting user-data returned by + * the provider into our KYC attribute data. + */ + char *attribute_template; + /** * Validity time for a successful KYC process. */ @@ -194,6 +201,11 @@ struct TALER_KYCLOGIC_ProofHandle */ char *post_body; + /** + * KYC attributes returned about the user by the OAuth 2.0 server. + */ + json_t *attributes; + /** * Response to return. */ @@ -277,6 +289,7 @@ oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) GNUNET_free (pd->client_id); GNUNET_free (pd->client_secret); GNUNET_free (pd->post_kyc_redirect_url); + GNUNET_free (pd->attribute_template); GNUNET_free (pd); } @@ -443,6 +456,21 @@ oauth2_load_configuration (void *cls, } pd->post_kyc_redirect_url = s; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ps->cfg, + provider_section_name, + "KYC_OAUTH2_ATTRIBUTE_TEMPLATE", + &s)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + provider_section_name, + "KYC_OAUTH2_ATTRIBUTE_TEMPLATE"); + } + else + { + pd->attribute_template = s; + } + return pd; } @@ -566,9 +594,12 @@ return_proof_response (void *cls) ph->provider_user_id, ph->provider_legitimization_id, GNUNET_TIME_relative_to_absolute (ph->pd->validity), + ph->attributes, ph->http_status, ph->response); GNUNET_free (ph->provider_user_id); + if (NULL != ph->attributes) + json_decref (ph->attributes); GNUNET_free (ph); } @@ -640,6 +671,57 @@ handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, } +/** + * Convert user data returned by the provider into + * standardized attribute data. + * + * @param pd our provider configuration + * @param data user-data given by the provider + * @return converted KYC attribute data object + */ +static json_t * +data2attributes (const struct TALER_KYCLOGIC_ProviderDetails *pd, + const json_t *data) +{ + json_t *ret; + void *attr_data; + size_t attr_size; + int rv; + json_error_t err; + + if (NULL == pd->attribute_template) + return json_object (); + if (0 != + (rv = TALER_TEMPLATING_fill (pd->attribute_template, + data, + &attr_data, + &attr_size))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to convert KYC provider data to attributes: %d\n", + rv); + json_dumpf (data, + stderr, + JSON_INDENT (2)); + return NULL; + } + ret = json_loadb (attr_data, + attr_size, + JSON_REJECT_DUPLICATES, + &err); + GNUNET_free (attr_data); + if (NULL == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse converted KYC attributes as JSON: %s (at offset %d)\n", + err.text, + err.position); + return NULL; + } + return ret; +} + + /** * The request for @a ph succeeded (presumably). * Call continuation with the result. @@ -689,6 +771,7 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, GNUNET_break_op (0); handle_proof_error (ph, j); + GNUNET_JSON_parse_free (spec); return; } { @@ -716,6 +799,7 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, "Unexpected response from KYC gateway: data must contain id"); ph->http_status = MHD_HTTP_BAD_GATEWAY; + GNUNET_JSON_parse_free (spec); return; } ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; @@ -731,6 +815,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, ph->http_status = MHD_HTTP_SEE_OTHER; ph->provider_user_id = GNUNET_strdup (id); } + ph->attributes = data2attributes (ph->pd, + data); + GNUNET_JSON_parse_free (spec); } diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c index abc8e78f5..9f3952558 100644 --- a/src/kyclogic/plugin_kyclogic_persona.c +++ b/src/kyclogic/plugin_kyclogic_persona.c @@ -890,6 +890,7 @@ proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph, account_id, inquiry_id, expiration, + NULL, /* FIXME: return attributes! */ http_status, resp); } @@ -1173,6 +1174,7 @@ handle_proof_finished (void *cls, account_id, inquiry_id, expiration, + NULL, /* FIXME: return attributes! */ MHD_HTTP_SEE_OTHER, resp); } diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index d436ef7ee..bb473c682 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -688,6 +688,7 @@ handler_kyc_webhook_post ( * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown * @param expiration until when is the KYC check valid + * @param attributes attributes about the user * @param http_status HTTP status code of @a response * @param[in] response to return to the HTTP client */ @@ -698,6 +699,7 @@ proof_cb ( const char *provider_user_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration, + const json_t *attributes, unsigned int http_status, struct MHD_Response *response) { @@ -710,6 +712,10 @@ proof_cb ( status, http_status, provider_user_id); + if (NULL != attributes) + json_dumpf (attributes, + stderr, + JSON_INDENT (2)); MHD_resume_connection (rs->rc->connection); TALER_MHD_daemon_trigger (); rs->rc->response = response; From 54fa07f5c7be025ee1a241deabe4a5dbcca61599 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 23 Jan 2023 07:57:34 -0500 Subject: [PATCH 09/10] tests for refunds_by_coin and ready_deposit --- src/exchangedb/Makefile.am | 10 +- .../exchange_do_get_ready_deposit.sql | 64 ++++++ src/exchangedb/exchange_do_refund_by_coin.sql | 94 +++++++++ src/exchangedb/pg_get_ready_deposit.c | 193 +++++++++++++++-- src/exchangedb/pg_select_refunds_by_coin.c | 196 ++++++++++++++++-- src/exchangedb/procedures.sql.in | 2 + .../test_exchangedb_populate_link_data.c | 29 +-- .../test_exchangedb_populate_ready_deposit.c | 39 ++-- ...hangedb_populate_select_refunds_by_coin.c} | 99 ++++++--- 9 files changed, 608 insertions(+), 118 deletions(-) create mode 100644 src/exchangedb/exchange_do_get_ready_deposit.sql create mode 100644 src/exchangedb/exchange_do_refund_by_coin.sql rename src/exchangedb/{test_exchangedb_populate_table.c => test_exchangedb_populate_select_refunds_by_coin.c} (87%) diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index feeb09cd1..2ae5fe426 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -296,7 +296,7 @@ check_PROGRAMS = \ perf-exchangedb-reserves-in-insert-postgres\ test-exchangedb-by-j-postgres\ test-exchangedb-batch-reserves-in-insert-postgres\ - test-exchangedb-populate-table-postgres\ + test-exchangedb-populate-select-refunds-by-coin-postgres\ test-exchangedb-populate-link-data-postgres\ test-exchangedb-populate-ready-deposit-postgres @@ -306,7 +306,7 @@ TESTS = \ test-exchangedb-by-j-postgres\ perf-exchangedb-reserves-in-insert-postgres\ test-exchangedb-batch-reserves-in-insert-postgres\ - test-exchangedb-populate-table-postgres\ + test-exchangedb-populate-select-refunds-by-coin-postgres\ test-exchangedb-populate-link-data-postgres\ test-exchangedb-populate-ready-deposit-postgres test_exchangedb_postgres_SOURCES = \ @@ -369,9 +369,9 @@ test_exchangedb_batch_reserves_in_insert_postgres_LDADD = \ -lgnunetutil \ $(XLIB) -test_exchangedb_populate_table_postgres_SOURCES = \ - test_exchangedb_populate_table.c -test_exchangedb_populate_table_postgres_LDADD = \ +test_exchangedb_populate_select_refunds_by_coin_postgres_SOURCES = \ + test_exchangedb_populate_select_refunds_by_coin.c +test_exchangedb_populate_select_refunds_by_coin_postgres_LDADD = \ libtalerexchangedb.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ diff --git a/src/exchangedb/exchange_do_get_ready_deposit.sql b/src/exchangedb/exchange_do_get_ready_deposit.sql new file mode 100644 index 000000000..b887571e4 --- /dev/null +++ b/src/exchangedb/exchange_do_get_ready_deposit.sql @@ -0,0 +1,64 @@ +-- +-- 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 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 +-- +CREATE OR REPLACE FUNCTION exchange_do_get_ready_deposit( + IN in_now INT8, + IN in_start_shard_now INT8, + IN in_end_shard_now INT8, + OUT out_payto_uri VARCHAR, + OUT out_merchant_pub BYTEA +) +LANGUAGE plpgsql +AS $$ +DECLARE + var_wire_target_h_payto BYTEA; +DECLARE + var_coin_pub BYTEA; +DECLARE + var_deposit_serial_id INT8; +BEGIN + +SELECT + coin_pub + ,deposit_serial_id + INTO + var_coin_pub + ,var_deposit_serial_id + FROM deposits_by_ready + WHERE wire_deadline <= in_now + AND shard >= in_start_shard_now + AND shard <=in_end_shard_now + ORDER BY + wire_deadline ASC + ,shard ASC; + +SELECT + merchant_pub + ,wire_target_h_payto + INTO + out_merchant_pub + ,var_wire_target_h_payto + FROM deposits + WHERE coin_pub=var_coin_pub + AND deposit_serial_id=var_deposit_serial_id; + +SELECT + payto_uri + INTO out_payto_uri + FROM wire_targets + WHERE wire_target_h_payto=var_wire_target_h_payto; + +RETURN; +END $$; diff --git a/src/exchangedb/exchange_do_refund_by_coin.sql b/src/exchangedb/exchange_do_refund_by_coin.sql new file mode 100644 index 000000000..ee00e2b5a --- /dev/null +++ b/src/exchangedb/exchange_do_refund_by_coin.sql @@ -0,0 +1,94 @@ +-- +-- 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 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 +-- +/*DROP FUNCTION exchange_do_refund_by_coin( + IN in_coin_pub BYTEA, + IN in_merchant_pub BYTEA, + IN in_h_contract BYTEA +);*/ +CREATE OR REPLACE FUNCTION exchange_do_refund_by_coin( + IN in_coin_pub BYTEA, + IN in_merchant_pub BYTEA, + IN in_h_contract BYTEA +) +RETURNS SETOF record +LANGUAGE plpgsql +AS $$ +DECLARE + curs CURSOR + FOR + SELECT + amount_with_fee_val + ,amount_with_fee_frac + ,deposit_serial_id + FROM refunds + WHERE coin_pub=in_coin_pub; +DECLARE + i RECORD; +BEGIN +OPEN curs; +LOOP + FETCH NEXT FROM curs INTO i; + EXIT WHEN NOT FOUND; + RETURN QUERY + SELECT + i.amount_with_fee_val + ,i.amount_with_fee_frac + FROM deposits + WHERE + coin_pub=in_coin_pub + AND merchant_pub=in_merchant_pub + AND h_contract_terms=in_h_contract + AND i.deposit_serial_id = deposit_serial_id; +END LOOP; +CLOSE curs; +END $$; + +/*RETURNS TABLE(amount_with_fee_val INT8, amount_with_fee_frac INT4) +LANGUAGE plpgsql +AS $$ +DECLARE + curs CURSOR + FOR + SELECT + r.amount_with_fee_val + ,r.amount_with_fee_frac + ,r.deposit_serial_id + FROM refunds r + WHERE r.coin_pub=in_coin_pub; +DECLARE + i RECORD; +BEGIN +OPEN curs; +LOOP + FETCH NEXT FROM curs INTO i; + IF FOUND + THEN + RETURN QUERY + SELECT + i.amount_with_fee_val + ,i.amount_with_fee_frac + FROM deposits + WHERE + merchant_pub=in_merchant_pub + AND h_contract_terms=in_h_contract + AND i.deposit_serial_id = deposit_serial_id; + END IF; + EXIT WHEN NOT FOUND; +END LOOP; +CLOSE curs; + +END $$; +*/ diff --git a/src/exchangedb/pg_get_ready_deposit.c b/src/exchangedb/pg_get_ready_deposit.c index af1235293..73ac9e47b 100644 --- a/src/exchangedb/pg_get_ready_deposit.c +++ b/src/exchangedb/pg_get_ready_deposit.c @@ -41,6 +41,7 @@ TEH_PG_get_ready_deposit (void *cls, GNUNET_PQ_query_param_uint64 (&end_shard_row), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", merchant_pub), @@ -57,26 +58,180 @@ TEH_PG_get_ready_deposit (void *cls, "Finding ready deposits by deadline %s (%llu)\n", GNUNET_TIME_absolute2s (now), (unsigned long long) now.abs_value_us); - PREPARE (pg, - "deposits_get_ready", - "SELECT" - " payto_uri" - ",merchant_pub" - " FROM deposits_by_ready dbr" - " JOIN deposits dep" - " ON (dbr.coin_pub = dep.coin_pub AND" - " dbr.deposit_serial_id = dep.deposit_serial_id)" - " JOIN wire_targets wt" - " USING (wire_target_h_payto)" - " WHERE dbr.wire_deadline<=$1" - " AND dbr.shard >= $2" - " AND dbr.shard <= $3" - " ORDER BY " - " dbr.wire_deadline ASC" - " ,dbr.shard ASC" - " LIMIT 1;"); + int choose_mode =-2; + const char *query; + + if (-2 == choose_mode) + { + const char *mode = getenv ("NEW_LOGIC"); + char dummy; + if ( (NULL==mode) || + (1 != sscanf (mode, + "%d%c", + &choose_mode, + &dummy)) ) + { + if (NULL != mode) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bad mode `%s' specified\n", + mode); + } + if (NULL==mode) + choose_mode=0; + + + } + switch (choose_mode) + { + case 0: + query="deposits_get_ready"; + PREPARE (pg, + query, + "SELECT" + " payto_uri" + ",merchant_pub" + " FROM deposits_by_ready dbr" + " JOIN deposits dep" + " ON (dbr.coin_pub = dep.coin_pub AND" + " dbr.deposit_serial_id = dep.deposit_serial_id)" + " JOIN wire_targets wt" + " USING (wire_target_h_payto)" + " WHERE dbr.wire_deadline<=$1" + " AND dbr.shard >= $2" + " AND dbr.shard <= $3" + " ORDER BY " + " dbr.wire_deadline ASC" + " ,dbr.shard ASC" + " LIMIT 1;"); + break; + case 1: + query="deposits_get_ready_v1"; + PREPARE (pg, + query, + "WITH rc AS MATERIALIZED (" + " SELECT" + " coin_pub" + ",deposit_serial_id" + " FROM deposits_by_ready" + " WHERE" + " wire_deadline<=$1" + " AND shard >= $2" + " AND shard <= $3" + " ORDER BY " + " wire_deadline ASC" + " ,shard ASC" + " LIMIT 1" + ")" + "SELECT" + " wt.payto_uri" + ",dep.merchant_pub" + " FROM (" + " SELECT" + " wire_target_h_payto" + ",merchant_pub" + " FROM deposits" + " WHERE coin_pub=(SELECT coin_pub FROM rc)" + " AND deposit_serial_id=(SELECT deposit_serial_id FROM rc)" + ") dep" + " JOIN wire_targets wt" + " ON (dep.wire_target_h_payto = wt.wire_target_h_payto)" + ); + + break; + case 2: + query = "stored_procedure_get_ready_deposit"; + PREPARE (pg, + query, + "SELECT" + " out_payto_uri AS payto_uri" + ",out_merchant_pub AS merchant_pub" + " FROM" + " exchange_do_get_ready_deposit" + " ($1, $2, $3) "); + break; + case 3: + query="deposits_get_ready_v3"; + PREPARE (pg, + query, + "WITH rc AS MATERIALIZED (" + " SELECT" + " coin_pub" + ",deposit_serial_id" + " FROM deposits_by_ready" + " WHERE" + " wire_deadline<=$1" + " AND shard >= $2" + " AND shard <= $3" + " ORDER BY " + " wire_deadline ASC" + " ,shard ASC" + " LIMIT 1" + ")" + "SELECT" + " wt.payto_uri" + ",dep.merchant_pub" + " FROM (" + " SELECT" + " wire_target_h_payto" + ",merchant_pub" + ",coin_pub" + " FROM deposits" + " WHERE coin_pub=(SELECT coin_pub FROM rc)" + " AND deposit_serial_id=(SELECT deposit_serial_id FROM rc)" + ") dep" + " JOIN wire_targets wt" + " ON (dep.wire_target_h_payto = wt.wire_target_h_payto)" + " JOIN rc" + " ON (dep.coin_pub=rc.coin_pub)" + ); + + break; + case 4: + query="deposits_get_ready_v4"; + PREPARE (pg, + query, + "WITH rc AS MATERIALIZED (" + " SELECT" + " coin_pub" + ",deposit_serial_id" + " FROM deposits_by_ready" + " WHERE" + " wire_deadline<=$1" + " AND shard >= $2" + " AND shard <= $3" + " ORDER BY " + " wire_deadline ASC" + " ,shard ASC" + " LIMIT 1" + ")," + "WITH rv AS MATERIALIZED (" + " SELECT" + " payto_uri" + ",wire_target_h_payto" + " FROM wire_targets" + ")" + "SELECT" + " rv.payto_uri" + ",dep.merchant_pub" + " FROM (" + " SELECT" + " wire_target_h_payto" + ",merchant_pub" + " FROM deposits" + " WHERE coin_pub=(SELECT coin_pub FROM rc)" + " AND deposit_serial_id=(SELECT deposit_serial_id FROM rc)" + ") dep" + " JOIN rv" + " ON (rv.wire_target_h_payto=dep.wire_target_h_payto)" + ); + break; + default: + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "deposits_get_ready", + query, params, rs); } diff --git a/src/exchangedb/pg_select_refunds_by_coin.c b/src/exchangedb/pg_select_refunds_by_coin.c index b9db2b709..255cac342 100644 --- a/src/exchangedb/pg_select_refunds_by_coin.c +++ b/src/exchangedb/pg_select_refunds_by_coin.c @@ -112,17 +112,47 @@ TEH_PG_select_refunds_by_coin ( GNUNET_PQ_query_param_auto_from_type (h_contract), GNUNET_PQ_query_param_end }; + struct GNUNET_PQ_QueryParam params5[] = { + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_end + }; + struct SelectRefundContext srctx = { .cb = cb, .cb_cls = cb_cls, .pg = pg, .status = GNUNET_OK }; + static int percent_refund = -2; + const char *query; + struct GNUNET_PQ_QueryParam *xparams = params; - if (NULL == getenv ("NEW_LOGIC")) + if (-2 == percent_refund) { + const char *mode = getenv ("NEW_LOGIC"); + char dummy; + + if ( (NULL==mode) || + (1 != sscanf (mode, + "%d%c", + &percent_refund, + &dummy)) ) + { + if (NULL != mode) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bad mode `%s' specified\n", + mode); + } + if (NULL==mode) + percent_refund=0; + } + + switch (percent_refund) + { + case 0: + query = "get_refunds_by_coin_and_contract-v0"; PREPARE (pg, - "get_refunds_by_coin_and_contract", + query, "SELECT" " ref.amount_with_fee_val" ",ref.amount_with_fee_frac" @@ -132,12 +162,26 @@ TEH_PG_select_refunds_by_coin ( " WHERE ref.coin_pub=$1" " AND dep.merchant_pub=$2" " AND dep.h_contract_terms=$3;"); - } - else - { - // FIXME-Joseph + break; + case 1: + query = "get_refunds_by_coin_and_contract-v1"; PREPARE (pg, - "get_refunds_by_coin_and_contract", + query, + "SELECT" + " ref.amount_with_fee_val" + ",ref.amount_with_fee_frac" + " FROM refunds ref" + " LEFT JOIN deposits dep" + " ON dep.coin_pub = ref.coin_pub" + " AND ref.deposit_serial_id = dep.deposit_serial_id" + " WHERE ref.coin_pub=$1" + " AND dep.merchant_pub=$2" + " AND dep.h_contract_terms=$3;"); + break; + case 2: + query = "get_refunds_by_coin_and_contract-v2"; + PREPARE (pg, + query, "WITH rc AS MATERIALIZED(" "SELECT" " amount_with_fee_val" @@ -149,21 +193,139 @@ TEH_PG_select_refunds_by_coin ( "SELECT" " rc.amount_with_fee_val" " ,rc.amount_with_fee_frac" - " FROM " - "(SELECT" + " FROM deposits dep" + " JOIN rc" + " ON rc.deposit_serial_id = dep.deposit_serial_id" + " WHERE" + " dep.coin_pub = $1" + " AND dep.merchant_pub = $2" + " AND dep.h_contract_terms = $3"); + break; + case 3: + query = "get_refunds_by_coin_and_contract-v3"; + PREPARE (pg, + query, + "WITH rc AS MATERIALIZED(" + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + ",deposit_serial_id" + " FROM refunds" + " WHERE coin_pub=$1)" + "SELECT" " rc.amount_with_fee_val" " ,rc.amount_with_fee_frac" - " FROM deposits dep" + " FROM (" + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + " FROM deposits depos" " WHERE" - " dep.coin_pub = $1" // optional... - " AND dep.merchant_pub = $2" - " AND dep.h_contract_terms = $3) dep" - " JOIN rc" - " USING (coin_pub,deposit_serial_id)"); + " depos.coin_pub = $1" + " AND depos.merchant_pub = $2" + " AND depos.h_contract_terms = $3) dep, rc;"); + break; + case 4: + query = "get_refunds_by_coin_and_contract-v4"; + PREPARE (pg, + query, + "WITH rc AS MATERIALIZED(" + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + ",coin_pub" + ",deposit_serial_id" + " FROM refunds ref" + " WHERE ref.coin_pub=$1)" + "SELECT" + " rc.amount_with_fee_val" + " ,rc.amount_with_fee_frac" + " ,deposit_serial_id" + " FROM (" + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + " FROM deposits depos" + " WHERE" + " depos.merchant_pub = $2" + " AND depos.h_contract_terms = $3) dep JOIN rc " + "USING(deposit_serial_id, coin_pub);"); + break; + case 5: + query = "get_refunds_by_coin_and_contract-v-broken"; + xparams = params5; + PREPARE (pg, + query, + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + ",coin_pub" + ",deposit_serial_id" + " FROM refunds" + " WHERE coin_pub=$1;"); + break; + case 8: + query = "get_refunds_by_coin_and_contract-v8"; + PREPARE (pg, + query, + "WITH" + " rc AS MATERIALIZED(" + " SELECT" + " amount_with_fee_val" + " ,amount_with_fee_frac" + " ,coin_pub" + " ,deposit_serial_id" + " FROM refunds" + " WHERE coin_pub=$1)," + " dep AS MATERIALIZED(" + " SELECT" + " deposit_serial_id" + " FROM deposits" + " WHERE coin_pub = $1" + " AND merchant_pub = $2" + " AND h_contract_terms = $3" + ")" + "SELECT" + " rc.amount_with_fee_val" + " ,rc.amount_with_fee_frac" + " FROM " + " rc JOIN dep USING (deposit_serial_id);"); + break; + case 9: + query = "get_refunds_by_coin_and_contract-v9-broken"; + PREPARE (pg, + query, + "SELECT" + " ref.amount_with_fee_val" + " ,ref.amount_with_fee_frac" + " FROM deposits dep" + " JOIN refunds ref USING(deposit_serial_id)" + " WHERE dep.coin_pub IN (" + " SELECT coin_pub" + " FROM refunds" + " WHERE coin_pub=$1)" + " AND merchant_pub = $2" + " AND h_contract_terms = $3;"); + break; + case 10: + query = "get_refunds_by_coin_and_contract-v10-broken"; + PREPARE (pg, + query, + "SELECT" + " *" + " FROM" + " exchange_do_refund_by_coin" + " ($1, $2, $3) " + " AS (amount_with_fee_val INT8, amount_with_fee_frac INT4);"); + break; + default: + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; } + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "get_refunds_by_coin_and_contract", - params, + query, + xparams, &get_refunds_cb, &srctx); if (GNUNET_SYSERR == srctx.status) diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in index 73b75af29..ef6341a1b 100644 --- a/src/exchangedb/procedures.sql.in +++ b/src/exchangedb/procedures.sql.in @@ -44,5 +44,7 @@ SET search_path TO exchange; #include "exchange_do_batch2_reserves_in_insert.sql" #include "exchange_do_batch4_reserves_in_insert.sql" #include "exchange_do_batch8_reserves_in_insert.sql" +#include "exchange_do_refund_by_coin.sql" +#include "exchange_do_get_ready_deposit.sql" COMMIT; diff --git a/src/exchangedb/test_exchangedb_populate_link_data.c b/src/exchangedb/test_exchangedb_populate_link_data.c index ff294874a..1323f3b3e 100644 --- a/src/exchangedb/test_exchangedb_populate_link_data.c +++ b/src/exchangedb/test_exchangedb_populate_link_data.c @@ -24,14 +24,6 @@ #include "taler_exchangedb_plugin.h" #include "math.h" - -#define NUM_ROWS 1000 - -/** - * Global result from the testcase. - */ -static int result; - /** * Report line of error if @a cond is true, and jump to label "drop". */ @@ -55,22 +47,10 @@ static int result; #define ZR_BLK(ptr) \ memset (ptr, 0, sizeof (*ptr)) - -/** - * Currency we use. Must match test-exchange-db-*.conf. - */ #define CURRENCY "EUR" - -/** - * How big do we make the RSA keys? - */ #define RSA_KEY_SIZE 1024 -static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; - -static struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA]; - -static struct TALER_TransferPublicKeyP tpub; #define ROUNDS 10 +#define NUM_ROWS 1000 #define MELT_NEW_COINS 5 #define MELT_NOREVEAL_INDEX 1 /** @@ -82,7 +62,10 @@ static struct TALER_DenomFeeSet fees; * Denomination keys used for fresh coins in melt test. */ static struct DenomKeyPair **new_dkp; - +static int result; +static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; +static struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA]; +static struct TALER_TransferPublicKeyP tpub; struct DenomKeyPair { struct TALER_DenominationPrivateKey priv; @@ -222,8 +205,6 @@ handle_link_data_cb (void *cls, } } - - /** * Main function that will be run by the scheduler. * diff --git a/src/exchangedb/test_exchangedb_populate_ready_deposit.c b/src/exchangedb/test_exchangedb_populate_ready_deposit.c index 49b1bf5d2..97273fc9e 100644 --- a/src/exchangedb/test_exchangedb_populate_ready_deposit.c +++ b/src/exchangedb/test_exchangedb_populate_ready_deposit.c @@ -24,9 +24,6 @@ #include "taler_exchangedb_plugin.h" #include "math.h" - -#define NUM_ROWS 1000 - /** * Global result from the testcase. */ @@ -55,20 +52,13 @@ static int result; #define ZR_BLK(ptr) \ memset (ptr, 0, sizeof (*ptr)) - /** * Currency we use. Must match test-exchange-db-*.conf. */ #define CURRENCY "EUR" - -/** - * How big do we make the RSA keys? - */ #define RSA_KEY_SIZE 1024 -static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; - - -#define ROUNDS 100 +#define NUM_ROWS 1000000 +#define ROUNDS 10000 #define MELT_NEW_COINS 5 #define MELT_NOREVEAL_INDEX 1 /** @@ -81,7 +71,7 @@ static struct TALER_MerchantWireHashP h_wire_wt; * Denomination keys used for fresh coins in melt test. */ static struct DenomKeyPair **new_dkp; - +static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; struct DenomKeyPair { struct TALER_DenominationPrivateKey priv; @@ -389,7 +379,6 @@ run (void *cls) &nonce_ok, &ruuid)); } - { /* ENSURE_COIN_KNOWN */ uint64_t known_coin_id; @@ -408,23 +397,23 @@ run (void *cls) refresh.noreveal_index = MELT_NOREVEAL_INDEX; } /*STORE INTO DEPOSIT*/ - { - struct GNUNET_TIME_Timestamp now; - now = GNUNET_TIME_timestamp_get (); - FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->insert_deposit (plugin->cls, - now, - &depos[i])); - } - if (ROUNDS == i) - TALER_denom_sig_free (&depos[i].coin.denom_sig); + { + struct GNUNET_TIME_Timestamp now; + now = GNUNET_TIME_timestamp_get (); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->insert_deposit (plugin->cls, + now, + &depos[i])); + } + if (ROUNDS == i) + TALER_denom_sig_free (&depos[i].coin.denom_sig); } /* End of benchmark setup */ GNUNET_free(perm); // commit FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != plugin->commit (plugin->cls)); - /**** CALL GET LINK DATA ****/ + /**** CALL GET READY DEPOSIT ****/ for (unsigned int r=0; r< ROUNDS; r++) { struct GNUNET_TIME_Absolute time; diff --git a/src/exchangedb/test_exchangedb_populate_table.c b/src/exchangedb/test_exchangedb_populate_select_refunds_by_coin.c similarity index 87% rename from src/exchangedb/test_exchangedb_populate_table.c rename to src/exchangedb/test_exchangedb_populate_select_refunds_by_coin.c index face454fd..c094b2048 100644 --- a/src/exchangedb/test_exchangedb_populate_table.c +++ b/src/exchangedb/test_exchangedb_populate_select_refunds_by_coin.c @@ -24,8 +24,6 @@ #include "taler_exchangedb_plugin.h" #include "math.h" -#define NUM_ROWS 10000 - /** * Global result from the testcase. */ @@ -41,7 +39,6 @@ static int result; goto drop; \ } while (0) - /** * Initializes @a ptr with random data. */ @@ -54,16 +51,13 @@ static int result; #define ZR_BLK(ptr) \ memset (ptr, 0, sizeof (*ptr)) - /** * Currency we use. Must match test-exchange-db-*.conf. */ #define CURRENCY "EUR" -/** - * How big do we make the RSA keys? - */ #define RSA_KEY_SIZE 1024 -#define ROUNDS 1000 +#define ROUNDS 10000 +#define NUM_ROWS 1000000 #define MELT_NEW_COINS 5 #define MELT_NOREVEAL_INDEX 1 /** @@ -72,14 +66,14 @@ static int result; static struct TALER_EXCHANGEDB_Plugin *plugin; static struct TALER_DenomFeeSet fees; static struct TALER_MerchantWireHashP h_wire_wt; - +static struct DenomKeyPair **new_dkp; +static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; struct DenomKeyPair { struct TALER_DenominationPrivateKey priv; struct TALER_DenominationPublicKey pub; }; -static struct DenomKeyPair **new_dkp; -static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; + /** * Destroy a denomination key pair. The key is not necessarily removed from the DB. * @@ -185,7 +179,6 @@ check_refund_cb (void *cls, const struct TALER_Amount *amount_with_fee) { const struct TALER_EXCHANGEDB_Refund *refund = cls; - if (0 != TALER_amount_cmp (amount_with_fee, &refund->details.refund_amount)) { @@ -207,7 +200,6 @@ run (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; const uint32_t num_partitions = 10; - struct DenomKeyPair *dkp = NULL; struct GNUNET_TIME_Timestamp ts; struct TALER_EXCHANGEDB_Deposit *depos=NULL; struct GNUNET_TIME_Timestamp deadline; @@ -226,6 +218,8 @@ run (void *cls) struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin; struct TALER_DenominationPublicKey *new_denom_pubs = NULL; + unsigned int count=0; + ref = GNUNET_new_array (ROUNDS +1, struct TALER_EXCHANGEDB_Refund); depos = GNUNET_new_array (ROUNDS +1, @@ -400,18 +394,72 @@ run (void *cls) now, &depos[i])); } - - /* 100% Refund */ { bool not_found; bool refund_ok; bool gone; bool conflict; - ref[i].coin = depos[i].coin; - ref[i].details.merchant_pub = depos[i].merchant_pub; + unsigned int refund_percent=0; + switch (refund_percent){ + case 2 ://100% refund + ref[i].coin = depos[i].coin; + ref[i].details.merchant_pub = depos[i].merchant_pub; + RND_BLK(&ref[i].details.merchant_sig); + ref[i].details.h_contract_terms = depos[i].h_contract_terms; + ref[i].coin.coin_pub = depos[i].coin.coin_pub; + ref[i].details.rtransaction_id = i; + ref[i].details.refund_amount = value; + ref[i].details.refund_fee = fees.refund; + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->do_refund (plugin->cls, + &ref[i], + &fees.deposit, + known_coin_id, + ¬_found, + &refund_ok, + &gone, + &conflict)); + break; + case 1 ://10% refund + if (count < (NUM_ROWS/10)) + { + ref[i].coin = depos[i].coin; + ref[i].details.merchant_pub = depos[i].merchant_pub; + RND_BLK(&ref[i].details.merchant_sig); + ref[i].details.h_contract_terms = depos[i].h_contract_terms; + ref[i].coin.coin_pub = depos[i].coin.coin_pub; + ref[i].details.rtransaction_id = i; + ref[i].details.refund_amount = value; + ref[i].details.refund_fee = fees.refund; + } + else + { + ref[i].coin = depos[i].coin; + RND_BLK(&ref[i].details.merchant_pub); + RND_BLK(&ref[i].details.merchant_sig); + RND_BLK(&ref[i].details.h_contract_terms); + RND_BLK(&ref[i].coin.coin_pub); + ref[i].details.rtransaction_id = i; + ref[i].details.refund_amount = value; + ref[i].details.refund_fee = fees.refund; + } + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + plugin->do_refund (plugin->cls, + &ref[i], + &fees.deposit, + known_coin_id, + ¬_found, + &refund_ok, + &gone, + &conflict)); + count++; + break; + case 0://no refund + ref[i].coin=depos[i].coin; + RND_BLK(&ref[i].details.merchant_pub); RND_BLK(&ref[i].details.merchant_sig); - ref[i].details.h_contract_terms = depos[i].h_contract_terms; - ref[i].coin.coin_pub = depos[i].coin.coin_pub; + RND_BLK(&ref[i].details.h_contract_terms); + RND_BLK(&ref[i].coin.coin_pub); ref[i].details.rtransaction_id = i; ref[i].details.refund_amount = value; ref[i].details.refund_fee = fees.refund; @@ -424,10 +472,8 @@ run (void *cls) &refund_ok, &gone, &conflict)); - - /* FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->insert_refund (plugin->cls, - &ref[i]));*/ + break; + }/* END OF SWITCH CASE */ } if (ROUNDS == i) TALER_denom_sig_free (&depos[i].coin.denom_sig); @@ -443,7 +489,7 @@ run (void *cls) struct GNUNET_TIME_Relative duration; time = GNUNET_TIME_absolute_get (); - FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + FAILIF (0 > plugin->select_refunds_by_coin (plugin->cls, &ref[r].coin.coin_pub, &ref[r].details.merchant_pub, @@ -476,10 +522,8 @@ run (void *cls) result = 0; drop: GNUNET_break (GNUNET_OK == - plugin->drop_tables (plugin->cls)); + plugin->drop_tables (plugin->cls)); cleanup: - if (NULL != dkp) - destroy_denom_key_pair (dkp); if (NULL != revealed_coins) { for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++) @@ -502,7 +546,6 @@ cleanup: } GNUNET_free(depos); GNUNET_free(ref); - dkp = NULL; TALER_EXCHANGEDB_plugin_unload (plugin); plugin = NULL; } From 10cf3b3b65c89b3304a4d4f40c4b1a7eb5e47d14 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 23 Jan 2023 09:14:25 -0500 Subject: [PATCH 10/10] fix SQL syntax --- contrib/gana | 2 +- src/exchangedb/exchange_do_insert_aml_decision.sql | 2 +- src/exchangedb/exchange_do_insert_aml_officer.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/gana b/contrib/gana index 7884adf99..3e659ed54 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 7884adf99ec4d5ccf52b1a5a251b99fb6ab9c2f6 +Subproject commit 3e659ed54023230dd45dbec5664f176e1763d260 diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql index 480736afa..b3f77c8c4 100644 --- a/src/exchangedb/exchange_do_insert_aml_decision.sql +++ b/src/exchangedb/exchange_do_insert_aml_decision.sql @@ -27,7 +27,7 @@ CREATE OR REPLACE FUNCTION exchange_do_insert_aml_decision( OUT out_last_date INT8) LANGUAGE plpgsql AS $$ - +BEGIN -- Check officer is eligible to make decisions. PERFORM FROM exchange.aml_staff diff --git a/src/exchangedb/exchange_do_insert_aml_officer.sql b/src/exchangedb/exchange_do_insert_aml_officer.sql index 72f813e89..5cb926c07 100644 --- a/src/exchangedb/exchange_do_insert_aml_officer.sql +++ b/src/exchangedb/exchange_do_insert_aml_officer.sql @@ -24,7 +24,7 @@ CREATE OR REPLACE FUNCTION exchange_do_insert_aml_officer( OUT out_last_change INT8) LANGUAGE plpgsql AS $$ - +BEGIN INSERT INTO exchange.aml_staff (decider_pub ,master_sig