Merge branch 'master' of git+ssh://git.taler.net/exchange

This commit is contained in:
Christian Grothoff 2023-01-27 10:12:08 +01:00
commit 7e8e2f4317
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
44 changed files with 1996 additions and 273 deletions

2
.gitmodules vendored
View File

@ -7,4 +7,4 @@
branch = prebuilt branch = prebuilt
[submodule "contrib/gana"] [submodule "contrib/gana"]
path = contrib/gana path = contrib/gana
url = https://git.gnunet.org/git/gana.git url = https://git.gnunet.org/gana.git

@ -1 +1 @@
Subproject commit 832685b6a942a6ebbec8e1e5b8c33b6b85b0a727 Subproject commit 3e659ed54023230dd45dbec5664f176e1763d260

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 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" #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(). * 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. * Next work item to perform.
*/ */
@ -508,6 +574,27 @@ static struct GNUNET_SCHEDULER_Task *nxt;
*/ */
static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh; 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. * Active denomiantion revocation requests.
*/ */
@ -629,6 +716,36 @@ do_shutdown (void *cls)
{ {
(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; struct DenomRevocationRequest *drr;
@ -842,6 +959,8 @@ static void
test_shutdown (void) test_shutdown (void)
{ {
if ( (NULL == drr_head) && if ( (NULL == drr_head) &&
(NULL == par_head) &&
(NULL == asr_head) &&
(NULL == srr_head) && (NULL == srr_head) &&
(NULL == aar_head) && (NULL == aar_head) &&
(NULL == adr_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. * Perform uploads based on the JSON in #out.
* *
@ -2267,6 +2601,14 @@ trigger_upload (const char *exchange_url)
.key = OP_EXTENSIONS, .key = OP_EXTENSIONS,
.cb = &upload_extensions .cb = &upload_extensions
}, },
{
.key = OP_UPDATE_AML_STAFF,
.cb = &update_aml_staff
},
{
.key = OP_ADD_PARTNER,
.cb = &add_partner
},
/* array termination */ /* array termination */
{ {
.key = NULL .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 * Function called with information about future keys. Dumps the JSON output
* (on success), either into an internal buffer or to stdout (depending on * (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)", "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 .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", .name = "upload",
.help = .help =

View File

@ -123,6 +123,7 @@ taler_exchange_wirewatch_LDADD = \
taler_exchange_httpd_SOURCES = \ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.h \ taler-exchange-httpd.c taler-exchange-httpd.h \
taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.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-deposit.c taler-exchange-httpd_batch-deposit.h \
taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.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 \ 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_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
taler-exchange-httpd_link.c taler-exchange-httpd_link.h \ taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
taler-exchange-httpd_management.h \ taler-exchange-httpd_management.h \
taler-exchange-httpd_management_aml-officers.c \
taler-exchange-httpd_management_auditors.c \ taler-exchange-httpd_management_auditors.c \
taler-exchange-httpd_management_auditors_AP_disable.c \ taler-exchange-httpd_management_auditors_AP_disable.c \
taler-exchange-httpd_management_denominations_HDP_revoke.c \ taler-exchange-httpd_management_denominations_HDP_revoke.c \
taler-exchange-httpd_management_drain.c \ taler-exchange-httpd_management_drain.c \
taler-exchange-httpd_management_extensions.c \ taler-exchange-httpd_management_extensions.c \
taler-exchange-httpd_management_global_fees.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_post_keys.c \
taler-exchange-httpd_management_signkey_EP_revoke.c \ taler-exchange-httpd_management_signkey_EP_revoke.c \
taler-exchange-httpd_management_wire_enable.c \ taler-exchange-httpd_management_wire_enable.c \

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 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_kyclogic_lib.h"
#include "taler_templating_lib.h" #include "taler_templating_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_auditors.h" #include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h" #include "taler-exchange-httpd_batch-deposit.h"
#include "taler-exchange-httpd_batch-withdraw.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. * Signature of functions that handle operations on reserves.
* *
@ -890,6 +1072,8 @@ handle_post_management (struct TEH_RequestContext *rc,
&exchange_pub, &exchange_pub,
root); 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], if (0 == strcmp (args[0],
"keys")) "keys"))
{ {
@ -967,6 +1151,30 @@ handle_post_management (struct TEH_RequestContext *rc,
return TEH_handler_management_post_drain (rc->connection, return TEH_handler_management_post_drain (rc->connection,
root); 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); GNUNET_break_op (0);
return r404 (rc->connection, return r404 (rc->connection,
"/management/*"); "/management/*");
@ -1289,6 +1497,22 @@ handle_mhd_request (void *cls,
.nargs = 4, .nargs = 4,
.nargs_is_upper_bound = true .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 */ /* mark end of list */
{ {
.url = NULL .url = NULL

View File

@ -213,7 +213,7 @@ struct TEH_RequestHandler
* *
* @param rc context for the request * @param rc context for the request
* @param json uploaded JSON data * @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 * @return MHD result code
*/ */
MHD_RESULT MHD_RESULT
@ -225,7 +225,7 @@ struct TEH_RequestHandler
* Function to call to handle DELETE requests. * Function to call to handle DELETE requests.
* *
* @param rc context for the request * @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 * @return MHD result code
*/ */
MHD_RESULT MHD_RESULT

View File

@ -30,28 +30,33 @@
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
/**
* How often do we try the DB operation at most?
*/
#define MAX_RETRIES 10
MHD_RESULT MHD_RESULT
TEH_handler_management_post_aml_decision ( TEH_handler_post_aml_decision (
struct MHD_Connection *connection, struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const json_t *root) const json_t *root)
{ {
struct MHD_Connection *connection = rc->connection;
const char *justification; const char *justification;
struct GNUNET_TIME_Timestamp decision_time; struct GNUNET_TIME_Timestamp decision_time;
struct TALER_Amount new_threshold; struct TALER_Amount new_threshold;
struct TALER_PaytoHashP h_payto; struct TALER_PaytoHashP h_payto;
uint32_t new_state32; uint32_t new_state32;
enum TALER_AmlDecisionState new_state; enum TALER_AmlDecisionState new_state;
struct TALER_AmlOfficerPublicKeyP officer_pub;
struct TALER_AmlOfficerSignatureP officer_sig; struct TALER_AmlOfficerSignatureP officer_sig;
struct GNUNET_JSON_Specification spec[] = { 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", GNUNET_JSON_spec_fixed_auto ("officer_sig",
&officer_sig), &officer_sig),
GNUNET_JSON_spec_fixed_auto ("h_payto", GNUNET_JSON_spec_fixed_auto ("h_payto",
&h_payto), &h_payto),
TALER_JSON_spec_amount ("new_threshold", TALER_JSON_spec_amount ("new_threshold",
TEH_currency,
&new_threshold), &new_threshold),
GNUNET_JSON_spec_string ("justification", GNUNET_JSON_spec_string ("justification",
&justification), &justification),
@ -76,13 +81,13 @@ TEH_handler_management_post_aml_decision (
new_state = (enum TALER_AmlDecisionState) new_state32; new_state = (enum TALER_AmlDecisionState) new_state32;
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK != if (GNUNET_OK !=
TALER_exchange_aml_decision_verify (justification, TALER_officer_aml_decision_verify (justification,
decision_time, decision_time,
&new_threshold, &new_threshold,
&h_payto, &h_payto,
new_state, new_state,
&officer_pub, officer_pub,
&officer_sig)) &officer_sig))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
@ -95,27 +100,29 @@ TEH_handler_management_post_aml_decision (
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp last_date; struct GNUNET_TIME_Timestamp last_date;
bool invalid_officer; bool invalid_officer;
unsigned int retries_left = MAX_RETRIES;
do { do {
qs = TEH_plugin->add_aml_decision (TEH_plugin->cls, qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
justification, &h_payto,
decision_time, &new_threshold,
&new_threshold, new_state,
&h_payto, decision_time,
new_state, justification,
&officer_pub, officer_pub,
&officer_sig, &officer_sig,
&invalid_officer, &invalid_officer,
&last_date); &last_date);
if (0 == --retries_left)
break;
} while (GNUNET_DB_STATUS_SOFT_ERROR == qs); } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (qs < 0) if (qs < 0)
{ {
GNUNET_break (0); GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_GENERIC_DB_STORE_FAILED,
"add aml_decision"); "add aml_decision");
return qs;
} }
if (invalid_officer) if (invalid_officer)
{ {
@ -127,7 +134,7 @@ TEH_handler_management_post_aml_decision (
} }
if (GNUNET_TIME_timestamp_cmp (last_date, if (GNUNET_TIME_timestamp_cmp (last_date,
>, >,
validity_start)) decision_time))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* @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 <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle an "/aml/$OFFICER_PUB/decision" request. Parses the decision
* details, checks the signatures and if appropriately authorized executes
* 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

View File

@ -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_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 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 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 http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client * @param[in] response to return to the HTTP client
*/ */
@ -179,6 +180,7 @@ proof_cb (
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration, struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status, unsigned int http_status,
struct MHD_Response *response) struct MHD_Response *response)
{ {
@ -194,6 +196,7 @@ proof_cb (
{ {
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
// FIXME: also store 'attributes' in DB!
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls, qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kpc->process_row, kpc->process_row,
kpc->provider_section, kpc->provider_section,

View File

@ -174,6 +174,32 @@ TEH_handler_management_post_drain (
const json_t *root); 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. * Initialize extension configuration handling.
* *

View File

@ -31,6 +31,12 @@
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
/**
* How often do we try the DB operation at most?
*/
#define MAX_RETRIES 10
MHD_RESULT MHD_RESULT
TEH_handler_management_aml_officers ( TEH_handler_management_aml_officers (
struct MHD_Connection *connection, struct MHD_Connection *connection,
@ -90,16 +96,19 @@ TEH_handler_management_aml_officers (
{ {
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp last_date; struct GNUNET_TIME_Timestamp last_date;
unsigned int retries_left = MAX_RETRIES;
do { do {
qs = TEH_plugin->set_aml_officer (TEH_plugin->cls, qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls,
&officer_pub, &officer_pub,
officer_name, &master_sig,
change_date, officer_name,
is_active, is_active,
read_only, read_only,
&master_sig, change_date,
&last_date); &last_date);
if (0 == --retries_left)
break;
} while (GNUNET_DB_STATUS_SOFT_ERROR == qs); } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (qs < 0) if (qs < 0)
{ {
@ -107,13 +116,13 @@ TEH_handler_management_aml_officers (
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_GENERIC_DB_STORE_FAILED,
"XXX"); "insert_aml_officer");
} }
if (GNUNET_TIME_timestamp_cmp (last_date, if (GNUNET_TIME_timestamp_cmp (last_date,
>, >,
change_date)) change_date))
{ {
GNUNER_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_CONFLICT, MHD_HTTP_CONFLICT,

View File

@ -51,13 +51,14 @@ TEH_handler_management_partners (
GNUNET_JSON_spec_string ("partner_base_url", GNUNET_JSON_spec_string ("partner_base_url",
&partner_base_url), &partner_base_url),
TALER_JSON_spec_amount ("wad_fee", TALER_JSON_spec_amount ("wad_fee",
TEH_currency,
&wad_fee), &wad_fee),
GNUNET_JSON_spec_timestamp ("start_date", GNUNET_JSON_spec_timestamp ("start_date",
&start_date), &start_date),
GNUNET_JSON_spec_timestamp ("end_date", GNUNET_JSON_spec_timestamp ("end_date",
&start_date), &start_date),
GNUNET_JSON_spec_time_rel ("wad_frequency", GNUNET_JSON_spec_relative_time ("wad_frequency",
&wad_frequency), &wad_frequency),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
@ -94,14 +95,14 @@ TEH_handler_management_partners (
{ {
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->add_partner (TEH_plugin->cls, qs = TEH_plugin->insert_partner (TEH_plugin->cls,
&partner_pub, &partner_pub,
start_date, start_date,
end_date, end_date,
wad_frequency, wad_frequency,
&wad_fee, &wad_fee,
partner_base_url, partner_base_url,
&master_sig); &master_sig);
if (qs < 0) if (qs < 0)
{ {
GNUNET_break (0); GNUNET_break (0);
@ -110,6 +111,14 @@ TEH_handler_management_partners (
TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_GENERIC_DB_STORE_FAILED,
"add_partner"); "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 ( return TALER_MHD_reply_static (
connection, connection,

View File

@ -25,6 +25,7 @@ CREATE TABLE partners
,wad_fee_frac INT4 NOT NULL ,wad_fee_frac INT4 NOT NULL
,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
,partner_base_url TEXT NOT NULL ,partner_base_url TEXT NOT NULL
,PRIMARY KEY (partner_master_pub, start_date)
); );
COMMENT ON TABLE partners COMMENT ON TABLE partners
IS 'exchanges we do wad transfers to'; IS 'exchanges we do wad transfers to';

View File

@ -110,7 +110,7 @@ BEGIN
EXECUTE FORMAT ( EXECUTE FORMAT (
'CREATE INDEX ' || table_name || '_main_index ' 'CREATE INDEX ' || table_name || '_main_index '
'ON ' || table_name || ' ' 'ON ' || table_name || ' '
'(h_payto ASC, decision_time ASC);' '(h_payto, decision_time DESC);'
); );
END $$; END $$;

View File

@ -296,7 +296,7 @@ check_PROGRAMS = \
perf-exchangedb-reserves-in-insert-postgres\ perf-exchangedb-reserves-in-insert-postgres\
test-exchangedb-by-j-postgres\ test-exchangedb-by-j-postgres\
test-exchangedb-batch-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-link-data-postgres\
test-exchangedb-populate-ready-deposit-postgres test-exchangedb-populate-ready-deposit-postgres
@ -306,7 +306,7 @@ TESTS = \
test-exchangedb-by-j-postgres\ test-exchangedb-by-j-postgres\
perf-exchangedb-reserves-in-insert-postgres\ perf-exchangedb-reserves-in-insert-postgres\
test-exchangedb-batch-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-link-data-postgres\
test-exchangedb-populate-ready-deposit-postgres test-exchangedb-populate-ready-deposit-postgres
test_exchangedb_postgres_SOURCES = \ test_exchangedb_postgres_SOURCES = \
@ -369,9 +369,9 @@ test_exchangedb_batch_reserves_in_insert_postgres_LDADD = \
-lgnunetutil \ -lgnunetutil \
$(XLIB) $(XLIB)
test_exchangedb_populate_table_postgres_SOURCES = \ test_exchangedb_populate_select_refunds_by_coin_postgres_SOURCES = \
test_exchangedb_populate_table.c test_exchangedb_populate_select_refunds_by_coin.c
test_exchangedb_populate_table_postgres_LDADD = \ test_exchangedb_populate_select_refunds_by_coin_postgres_LDADD = \
libtalerexchangedb.la \ libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \

View File

@ -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 <http://www.gnu.org/licenses/>
--
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 $$;

View File

@ -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 <http://www.gnu.org/licenses/>
--
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 $$
BEGIN
-- 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';

View File

@ -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 <http://www.gnu.org/licenses/>
--
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 $$
BEGIN
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';

View File

@ -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 <http://www.gnu.org/licenses/>
--
/*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 $$;
*/

View File

@ -41,6 +41,7 @@ TEH_PG_get_ready_deposit (void *cls,
GNUNET_PQ_query_param_uint64 (&end_shard_row), GNUNET_PQ_query_param_uint64 (&end_shard_row),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
merchant_pub), merchant_pub),
@ -57,26 +58,180 @@ TEH_PG_get_ready_deposit (void *cls,
"Finding ready deposits by deadline %s (%llu)\n", "Finding ready deposits by deadline %s (%llu)\n",
GNUNET_TIME_absolute2s (now), GNUNET_TIME_absolute2s (now),
(unsigned long long) now.abs_value_us); (unsigned long long) now.abs_value_us);
PREPARE (pg, int choose_mode =-2;
"deposits_get_ready", const char *query;
"SELECT"
" payto_uri" if (-2 == choose_mode)
",merchant_pub" {
" FROM deposits_by_ready dbr" const char *mode = getenv ("NEW_LOGIC");
" JOIN deposits dep" char dummy;
" ON (dbr.coin_pub = dep.coin_pub AND" if ( (NULL==mode) ||
" dbr.deposit_serial_id = dep.deposit_serial_id)" (1 != sscanf (mode,
" JOIN wire_targets wt" "%d%c",
" USING (wire_target_h_payto)" &choose_mode,
" WHERE dbr.wire_deadline<=$1" &dummy)) )
" AND dbr.shard >= $2" {
" AND dbr.shard <= $3" if (NULL != mode)
" ORDER BY " GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
" dbr.wire_deadline ASC" "Bad mode `%s' specified\n",
" ,dbr.shard ASC" mode);
" LIMIT 1;"); }
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, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"deposits_get_ready", query,
params, params,
rs); rs);
} }

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 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_PaytoHashP *h_payto,
const struct TALER_Amount *new_threshold, const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_status, enum TALER_AmlDecisionState new_status,
struct GNUNET_TIME_Absolute decision_time, struct GNUNET_TIME_Timestamp decision_time,
const char *justification, const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub, 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; struct PostgresClosure *pg = cls;
uint32_t ns = (uint32_t) new_status; uint32_t ns = (uint32_t) new_status;
@ -43,27 +45,29 @@ TEH_PG_insert_aml_decision (
GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_auto_from_type (h_payto),
TALER_PQ_query_param_amount (new_threshold), TALER_PQ_query_param_amount (new_threshold),
GNUNET_PQ_query_param_uint32 (&ns), 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_string (justification),
GNUNET_PQ_query_param_auto_from_type (decider_pub), GNUNET_PQ_query_param_auto_from_type (decider_pub),
GNUNET_PQ_query_param_auto_from_type (decider_sig), GNUNET_PQ_query_param_auto_from_type (decider_sig),
GNUNET_PQ_query_param_end 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
};
PREPARE (pg, PREPARE (pg,
"insert_aml_decision", "do_insert_aml_decision",
"INSERT INTO aml_history " "SELECT"
"(h_payto" " out_invalid_officer"
",new_threshold_val" ",out_last_date"
",new_threshold_frac" " FROM exchange_do_insert_aml_decision"
",new_status"
",decision_time"
",justification"
",decider_pub"
",decider_sig"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8);"); "($1, $2, $3, $4, $5, $6, $7, $8);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_aml_decision", "do_insert_aml_decision",
params); params,
rs);
} }

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 terms of the GNU General Public License as published by the Free Software
@ -38,6 +38,9 @@
* @param justification human-readable text justifying the decision * @param justification human-readable text justifying the decision
* @param decider_pub public key of the staff member * @param decider_pub public key of the staff member
* @param decider_sig signature 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 * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
@ -46,10 +49,12 @@ TEH_PG_insert_aml_decision (
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *new_threshold, const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_status, enum TALER_AmlDecisionState new_status,
struct GNUNET_TIME_Absolute decision_time, struct GNUNET_TIME_Timestamp decision_time,
const char *justification, const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub, 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 #endif

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 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, const char *decider_name,
bool is_active, bool is_active,
bool read_only, 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 PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
@ -43,22 +44,23 @@ TEH_PG_insert_aml_officer (
GNUNET_PQ_query_param_string (decider_name), GNUNET_PQ_query_param_string (decider_name),
GNUNET_PQ_query_param_bool (is_active), GNUNET_PQ_query_param_bool (is_active),
GNUNET_PQ_query_param_bool (read_only), 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 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
};
PREPARE (pg, PREPARE (pg,
"insert_aml_staff", "do_insert_aml_staff",
"INSERT INTO aml_staff " "SELECT"
"(decider_pub" " out_last_change"
",master_sig" " FROM exchange_do_insert_aml_officer"
",decider_name"
",is_active"
",read_only"
",last_change"
") VALUES "
"($1, $2, $3, $4, $5, $6);"); "($1, $2, $3, $4, $5, $6);");
return GNUNET_PQ_eval_prepared_non_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_aml_staff", "do_insert_aml_staff",
params); params,
rs);
} }

View File

@ -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 cls closure
* @param decider_pub public key of the staff member * @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 is_active true to enable, false to set as inactive
* @param read_only true to set read-only access * @param read_only true to set read-only access
* @param last_change when was the change made effective * @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 * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
@ -46,6 +50,7 @@ TEH_PG_insert_aml_officer (
const char *decider_name, const char *decider_name,
bool is_active, bool is_active,
bool read_only, bool read_only,
struct GNUNET_TIME_Absolute last_change); struct GNUNET_TIME_Timestamp last_change,
struct GNUNET_TIME_Timestamp *previous_change);
#endif #endif

View File

@ -28,13 +28,13 @@
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
TEH_PG_insert_partner (void *cls, TEH_PG_insert_partner (void *cls,
const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterPublicKeyP *master_pub,
struct GNUNET_TIME_Timestamp start_date, struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date, struct GNUNET_TIME_Timestamp end_date,
struct GNUNET_TIME_Relative wad_frequency, struct GNUNET_TIME_Relative wad_frequency,
const struct TALER_Amount *wad_fee, const struct TALER_Amount *wad_fee,
const char *partner_base_url, const char *partner_base_url,
const struct TALER_MasterSignatureP *master_sig) const struct TALER_MasterSignatureP *master_sig)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
@ -61,10 +61,9 @@ TEH_PG_insert_partner (void *cls,
" ,master_sig" " ,master_sig"
" ,partner_base_url" " ,partner_base_url"
" ) VALUES " " ) 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, return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_partner", "insert_partner",
params); params);
} }

View File

@ -59,7 +59,15 @@ TEH_PG_persist_policy_details (
GNUNET_PQ_result_spec_end 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, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_insert_or_update_policy_details", "call_insert_or_update_policy_details",
params, params,

View File

@ -112,17 +112,47 @@ TEH_PG_select_refunds_by_coin (
GNUNET_PQ_query_param_auto_from_type (h_contract), GNUNET_PQ_query_param_auto_from_type (h_contract),
GNUNET_PQ_query_param_end 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 = { struct SelectRefundContext srctx = {
.cb = cb, .cb = cb,
.cb_cls = cb_cls, .cb_cls = cb_cls,
.pg = pg, .pg = pg,
.status = GNUNET_OK .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, PREPARE (pg,
"get_refunds_by_coin_and_contract", query,
"SELECT" "SELECT"
" ref.amount_with_fee_val" " ref.amount_with_fee_val"
",ref.amount_with_fee_frac" ",ref.amount_with_fee_frac"
@ -132,12 +162,26 @@ TEH_PG_select_refunds_by_coin (
" WHERE ref.coin_pub=$1" " WHERE ref.coin_pub=$1"
" AND dep.merchant_pub=$2" " AND dep.merchant_pub=$2"
" AND dep.h_contract_terms=$3;"); " AND dep.h_contract_terms=$3;");
} break;
else case 1:
{ query = "get_refunds_by_coin_and_contract-v1";
// FIXME-Joseph
PREPARE (pg, 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(" "WITH rc AS MATERIALIZED("
"SELECT" "SELECT"
" amount_with_fee_val" " amount_with_fee_val"
@ -149,21 +193,139 @@ TEH_PG_select_refunds_by_coin (
"SELECT" "SELECT"
" rc.amount_with_fee_val" " rc.amount_with_fee_val"
" ,rc.amount_with_fee_frac" " ,rc.amount_with_fee_frac"
" FROM " " FROM deposits dep"
"(SELECT" " 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_val"
" ,rc.amount_with_fee_frac" " ,rc.amount_with_fee_frac"
" FROM deposits dep" " FROM ("
"SELECT"
" amount_with_fee_val"
",amount_with_fee_frac"
" FROM deposits depos"
" WHERE" " WHERE"
" dep.coin_pub = $1" // optional... " depos.coin_pub = $1"
" AND dep.merchant_pub = $2" " AND depos.merchant_pub = $2"
" AND dep.h_contract_terms = $3) dep" " AND depos.h_contract_terms = $3) dep, rc;");
" JOIN rc" break;
" USING (coin_pub,deposit_serial_id)"); 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, qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"get_refunds_by_coin_and_contract", query,
params, xparams,
&get_refunds_cb, &get_refunds_cb,
&srctx); &srctx);
if (GNUNET_SYSERR == srctx.status) if (GNUNET_SYSERR == srctx.status)

View File

@ -37,10 +37,14 @@ SET search_path TO exchange;
#include "exchange_do_reserve_open_deposit.sql" #include "exchange_do_reserve_open_deposit.sql"
#include "exchange_do_reserve_open.sql" #include "exchange_do_reserve_open.sql"
#include "exchange_do_insert_or_update_policy_details.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_in_insert.sql"
#include "exchange_do_batch_reserves_update.sql" #include "exchange_do_batch_reserves_update.sql"
#include "exchange_do_batch2_reserves_in_insert.sql" #include "exchange_do_batch2_reserves_in_insert.sql"
#include "exchange_do_batch4_reserves_in_insert.sql" #include "exchange_do_batch4_reserves_in_insert.sql"
#include "exchange_do_batch8_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; COMMIT;

View File

@ -24,14 +24,6 @@
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
#include "math.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". * 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) \ #define ZR_BLK(ptr) \
memset (ptr, 0, sizeof (*ptr)) memset (ptr, 0, sizeof (*ptr))
/**
* Currency we use. Must match test-exchange-db-*.conf.
*/
#define CURRENCY "EUR" #define CURRENCY "EUR"
/**
* How big do we make the RSA keys?
*/
#define RSA_KEY_SIZE 1024 #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 ROUNDS 10
#define NUM_ROWS 1000
#define MELT_NEW_COINS 5 #define MELT_NEW_COINS 5
#define MELT_NOREVEAL_INDEX 1 #define MELT_NOREVEAL_INDEX 1
/** /**
@ -82,7 +62,10 @@ static struct TALER_DenomFeeSet fees;
* Denomination keys used for fresh coins in melt test. * Denomination keys used for fresh coins in melt test.
*/ */
static struct DenomKeyPair **new_dkp; 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 DenomKeyPair
{ {
struct TALER_DenominationPrivateKey priv; struct TALER_DenominationPrivateKey priv;
@ -222,8 +205,6 @@ handle_link_data_cb (void *cls,
} }
} }
/** /**
* Main function that will be run by the scheduler. * Main function that will be run by the scheduler.
* *

View File

@ -24,9 +24,6 @@
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
#include "math.h" #include "math.h"
#define NUM_ROWS 1000
/** /**
* Global result from the testcase. * Global result from the testcase.
*/ */
@ -55,20 +52,13 @@ static int result;
#define ZR_BLK(ptr) \ #define ZR_BLK(ptr) \
memset (ptr, 0, sizeof (*ptr)) memset (ptr, 0, sizeof (*ptr))
/** /**
* Currency we use. Must match test-exchange-db-*.conf. * Currency we use. Must match test-exchange-db-*.conf.
*/ */
#define CURRENCY "EUR" #define CURRENCY "EUR"
/**
* How big do we make the RSA keys?
*/
#define RSA_KEY_SIZE 1024 #define RSA_KEY_SIZE 1024
static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins; #define NUM_ROWS 1000000
#define ROUNDS 10000
#define ROUNDS 100
#define MELT_NEW_COINS 5 #define MELT_NEW_COINS 5
#define MELT_NOREVEAL_INDEX 1 #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. * Denomination keys used for fresh coins in melt test.
*/ */
static struct DenomKeyPair **new_dkp; static struct DenomKeyPair **new_dkp;
static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
struct DenomKeyPair struct DenomKeyPair
{ {
struct TALER_DenominationPrivateKey priv; struct TALER_DenominationPrivateKey priv;
@ -389,7 +379,6 @@ run (void *cls)
&nonce_ok, &nonce_ok,
&ruuid)); &ruuid));
} }
{ {
/* ENSURE_COIN_KNOWN */ /* ENSURE_COIN_KNOWN */
uint64_t known_coin_id; uint64_t known_coin_id;
@ -408,23 +397,23 @@ run (void *cls)
refresh.noreveal_index = MELT_NOREVEAL_INDEX; refresh.noreveal_index = MELT_NOREVEAL_INDEX;
} }
/*STORE INTO DEPOSIT*/ /*STORE INTO DEPOSIT*/
{ {
struct GNUNET_TIME_Timestamp now; struct GNUNET_TIME_Timestamp now;
now = GNUNET_TIME_timestamp_get (); now = GNUNET_TIME_timestamp_get ();
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_deposit (plugin->cls, plugin->insert_deposit (plugin->cls,
now, now,
&depos[i])); &depos[i]));
} }
if (ROUNDS == i) if (ROUNDS == i)
TALER_denom_sig_free (&depos[i].coin.denom_sig); TALER_denom_sig_free (&depos[i].coin.denom_sig);
} }
/* End of benchmark setup */ /* End of benchmark setup */
GNUNET_free(perm); GNUNET_free(perm);
// commit // commit
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->commit (plugin->cls)); plugin->commit (plugin->cls));
/**** CALL GET LINK DATA ****/ /**** CALL GET READY DEPOSIT ****/
for (unsigned int r=0; r< ROUNDS; r++) for (unsigned int r=0; r< ROUNDS; r++)
{ {
struct GNUNET_TIME_Absolute time; struct GNUNET_TIME_Absolute time;

View File

@ -24,8 +24,6 @@
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
#include "math.h" #include "math.h"
#define NUM_ROWS 10000
/** /**
* Global result from the testcase. * Global result from the testcase.
*/ */
@ -41,7 +39,6 @@ static int result;
goto drop; \ goto drop; \
} while (0) } while (0)
/** /**
* Initializes @a ptr with random data. * Initializes @a ptr with random data.
*/ */
@ -54,16 +51,13 @@ static int result;
#define ZR_BLK(ptr) \ #define ZR_BLK(ptr) \
memset (ptr, 0, sizeof (*ptr)) memset (ptr, 0, sizeof (*ptr))
/** /**
* Currency we use. Must match test-exchange-db-*.conf. * Currency we use. Must match test-exchange-db-*.conf.
*/ */
#define CURRENCY "EUR" #define CURRENCY "EUR"
/**
* How big do we make the RSA keys?
*/
#define RSA_KEY_SIZE 1024 #define RSA_KEY_SIZE 1024
#define ROUNDS 1000 #define ROUNDS 10000
#define NUM_ROWS 1000000
#define MELT_NEW_COINS 5 #define MELT_NEW_COINS 5
#define MELT_NOREVEAL_INDEX 1 #define MELT_NOREVEAL_INDEX 1
/** /**
@ -72,14 +66,14 @@ static int result;
static struct TALER_EXCHANGEDB_Plugin *plugin; static struct TALER_EXCHANGEDB_Plugin *plugin;
static struct TALER_DenomFeeSet fees; static struct TALER_DenomFeeSet fees;
static struct TALER_MerchantWireHashP h_wire_wt; static struct TALER_MerchantWireHashP h_wire_wt;
static struct DenomKeyPair **new_dkp;
static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
struct DenomKeyPair struct DenomKeyPair
{ {
struct TALER_DenominationPrivateKey priv; struct TALER_DenominationPrivateKey priv;
struct TALER_DenominationPublicKey pub; 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. * 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_Amount *amount_with_fee)
{ {
const struct TALER_EXCHANGEDB_Refund *refund = cls; const struct TALER_EXCHANGEDB_Refund *refund = cls;
if (0 != TALER_amount_cmp (amount_with_fee, if (0 != TALER_amount_cmp (amount_with_fee,
&refund->details.refund_amount)) &refund->details.refund_amount))
{ {
@ -207,7 +200,6 @@ run (void *cls)
{ {
struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct GNUNET_CONFIGURATION_Handle *cfg = cls;
const uint32_t num_partitions = 10; const uint32_t num_partitions = 10;
struct DenomKeyPair *dkp = NULL;
struct GNUNET_TIME_Timestamp ts; struct GNUNET_TIME_Timestamp ts;
struct TALER_EXCHANGEDB_Deposit *depos=NULL; struct TALER_EXCHANGEDB_Deposit *depos=NULL;
struct GNUNET_TIME_Timestamp deadline; struct GNUNET_TIME_Timestamp deadline;
@ -226,6 +218,8 @@ run (void *cls)
struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin; struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
struct TALER_DenominationPublicKey *new_denom_pubs = NULL; struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
unsigned int count=0;
ref = GNUNET_new_array (ROUNDS +1, ref = GNUNET_new_array (ROUNDS +1,
struct TALER_EXCHANGEDB_Refund); struct TALER_EXCHANGEDB_Refund);
depos = GNUNET_new_array (ROUNDS +1, depos = GNUNET_new_array (ROUNDS +1,
@ -400,18 +394,72 @@ run (void *cls)
now, now,
&depos[i])); &depos[i]));
} }
/* 100% Refund */
{ {
bool not_found; bool not_found;
bool refund_ok; bool refund_ok;
bool gone; bool gone;
bool conflict; bool conflict;
ref[i].coin = depos[i].coin; unsigned int refund_percent=0;
ref[i].details.merchant_pub = depos[i].merchant_pub; 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,
&not_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,
&not_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); RND_BLK(&ref[i].details.merchant_sig);
ref[i].details.h_contract_terms = depos[i].h_contract_terms; RND_BLK(&ref[i].details.h_contract_terms);
ref[i].coin.coin_pub = depos[i].coin.coin_pub; RND_BLK(&ref[i].coin.coin_pub);
ref[i].details.rtransaction_id = i; ref[i].details.rtransaction_id = i;
ref[i].details.refund_amount = value; ref[i].details.refund_amount = value;
ref[i].details.refund_fee = fees.refund; ref[i].details.refund_fee = fees.refund;
@ -424,10 +472,8 @@ run (void *cls)
&refund_ok, &refund_ok,
&gone, &gone,
&conflict)); &conflict));
break;
/* FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != }/* END OF SWITCH CASE */
plugin->insert_refund (plugin->cls,
&ref[i]));*/
} }
if (ROUNDS == i) if (ROUNDS == i)
TALER_denom_sig_free (&depos[i].coin.denom_sig); TALER_denom_sig_free (&depos[i].coin.denom_sig);
@ -443,7 +489,7 @@ run (void *cls)
struct GNUNET_TIME_Relative duration; struct GNUNET_TIME_Relative duration;
time = GNUNET_TIME_absolute_get (); time = GNUNET_TIME_absolute_get ();
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (0 >
plugin->select_refunds_by_coin (plugin->cls, plugin->select_refunds_by_coin (plugin->cls,
&ref[r].coin.coin_pub, &ref[r].coin.coin_pub,
&ref[r].details.merchant_pub, &ref[r].details.merchant_pub,
@ -476,10 +522,8 @@ run (void *cls)
result = 0; result = 0;
drop: drop:
GNUNET_break (GNUNET_OK == GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls)); plugin->drop_tables (plugin->cls));
cleanup: cleanup:
if (NULL != dkp)
destroy_denom_key_pair (dkp);
if (NULL != revealed_coins) if (NULL != revealed_coins)
{ {
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++) for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
@ -502,7 +546,6 @@ cleanup:
} }
GNUNET_free(depos); GNUNET_free(depos);
GNUNET_free(ref); GNUNET_free(ref);
dkp = NULL;
TALER_EXCHANGEDB_plugin_unload (plugin); TALER_EXCHANGEDB_plugin_unload (plugin);
plugin = NULL; plugin = NULL;
} }

View File

@ -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 * @param rh handle of the operation to cancel
*/ */

View File

@ -6602,6 +6602,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param is_active true to enable, false to set as inactive * @param is_active true to enable, false to set as inactive
* @param read_only true to set read-only access * @param read_only true to set read-only access
* @param last_change when was the change made effective * @param last_change when was the change made effective
* @param[out] previous_change when was the previous change made
* @return database transaction status * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
@ -6612,7 +6613,8 @@ struct TALER_EXCHANGEDB_Plugin
const char *decider_name, const char *decider_name,
bool is_active, bool is_active,
bool read_only, 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 justification human-readable text justifying the decision
* @param decider_pub public key of the staff member * @param decider_pub public key of the staff member
* @param decider_sig signature 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 * @return database transaction status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
@ -6735,10 +6740,12 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *new_threshold, const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_status, enum TALER_AmlDecisionState new_status,
struct GNUNET_TIME_Absolute decision_time, struct GNUNET_TIME_Timestamp decision_time,
const char *justification, const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub, 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);
}; };

View File

@ -158,6 +158,7 @@ typedef void
* @param status KYC status * @param status KYC status
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown * @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 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 expiration until when is the KYC check valid
* @param http_status HTTP status code of @a response * @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client * @param[in] response to return to the HTTP client
@ -169,6 +170,7 @@ typedef void
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration, struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status, unsigned int http_status,
struct MHD_Response *response); struct MHD_Response *response);

View File

@ -79,6 +79,7 @@ libtaler_plugin_kyclogic_oauth2_la_LIBADD = \
$(LTLIBINTL) $(LTLIBINTL)
libtaler_plugin_kyclogic_oauth2_la_LDFLAGS = \ libtaler_plugin_kyclogic_oauth2_la_LDFLAGS = \
$(TALER_PLUGIN_LDFLAGS) \ $(TALER_PLUGIN_LDFLAGS) \
$(top_builddir)/src/templating/libtalertemplating.la \
$(top_builddir)/src/mhd/libtalermhd.la \ $(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \

View File

@ -25,3 +25,11 @@ KYC_OAUTH2_POST_URL = http://example.com/thank-you
# For authentication to the OAuth2.0 service # For authentication to the OAuth2.0 service
KYC_OAUTH2_CLIENT_ID = testcase KYC_OAUTH2_CLIENT_ID = testcase
KYC_OAUTH2_CLIENT_SECRET = password 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}}"}"

View File

@ -632,6 +632,7 @@ proof_reply (void *cls)
NULL, /* user id */ NULL, /* user id */
NULL, /* provider legi ID */ NULL, /* provider legi ID */
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
NULL, /* attributes */
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
resp); resp);
} }

View File

@ -21,6 +21,7 @@
#include "platform.h" #include "platform.h"
#include "taler_kyclogic_plugin.h" #include "taler_kyclogic_plugin.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_templating_lib.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include <regex.h> #include <regex.h>
#include "taler_util.h" #include "taler_util.h"
@ -105,6 +106,12 @@ struct TALER_KYCLOGIC_ProviderDetails
*/ */
char *post_kyc_redirect_url; 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. * Validity time for a successful KYC process.
*/ */
@ -194,6 +201,11 @@ struct TALER_KYCLOGIC_ProofHandle
*/ */
char *post_body; char *post_body;
/**
* KYC attributes returned about the user by the OAuth 2.0 server.
*/
json_t *attributes;
/** /**
* Response to return. * Response to return.
*/ */
@ -277,6 +289,7 @@ oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
GNUNET_free (pd->client_id); GNUNET_free (pd->client_id);
GNUNET_free (pd->client_secret); GNUNET_free (pd->client_secret);
GNUNET_free (pd->post_kyc_redirect_url); GNUNET_free (pd->post_kyc_redirect_url);
GNUNET_free (pd->attribute_template);
GNUNET_free (pd); GNUNET_free (pd);
} }
@ -443,6 +456,21 @@ oauth2_load_configuration (void *cls,
} }
pd->post_kyc_redirect_url = s; 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; return pd;
} }
@ -566,9 +594,12 @@ return_proof_response (void *cls)
ph->provider_user_id, ph->provider_user_id,
ph->provider_legitimization_id, ph->provider_legitimization_id,
GNUNET_TIME_relative_to_absolute (ph->pd->validity), GNUNET_TIME_relative_to_absolute (ph->pd->validity),
ph->attributes,
ph->http_status, ph->http_status,
ph->response); ph->response);
GNUNET_free (ph->provider_user_id); GNUNET_free (ph->provider_user_id);
if (NULL != ph->attributes)
json_decref (ph->attributes);
GNUNET_free (ph); 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). * The request for @a ph succeeded (presumably).
* Call continuation with the result. * Call continuation with the result.
@ -689,6 +771,7 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
GNUNET_break_op (0); GNUNET_break_op (0);
handle_proof_error (ph, handle_proof_error (ph,
j); j);
GNUNET_JSON_parse_free (spec);
return; return;
} }
{ {
@ -716,6 +799,7 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
"Unexpected response from KYC gateway: data must contain id"); "Unexpected response from KYC gateway: data must contain id");
ph->http_status ph->http_status
= MHD_HTTP_BAD_GATEWAY; = MHD_HTTP_BAD_GATEWAY;
GNUNET_JSON_parse_free (spec);
return; return;
} }
ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; 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->http_status = MHD_HTTP_SEE_OTHER;
ph->provider_user_id = GNUNET_strdup (id); ph->provider_user_id = GNUNET_strdup (id);
} }
ph->attributes = data2attributes (ph->pd,
data);
GNUNET_JSON_parse_free (spec);
} }

View File

@ -890,6 +890,7 @@ proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
account_id, account_id,
inquiry_id, inquiry_id,
expiration, expiration,
NULL, /* FIXME: return attributes! */
http_status, http_status,
resp); resp);
} }
@ -1173,6 +1174,7 @@ handle_proof_finished (void *cls,
account_id, account_id,
inquiry_id, inquiry_id,
expiration, expiration,
NULL, /* FIXME: return attributes! */
MHD_HTTP_SEE_OTHER, MHD_HTTP_SEE_OTHER,
resp); resp);
} }

View File

@ -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_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 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 expiration until when is the KYC check valid
* @param attributes attributes about the user
* @param http_status HTTP status code of @a response * @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client * @param[in] response to return to the HTTP client
*/ */
@ -698,6 +699,7 @@ proof_cb (
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration, struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status, unsigned int http_status,
struct MHD_Response *response) struct MHD_Response *response)
{ {
@ -710,6 +712,10 @@ proof_cb (
status, status,
http_status, http_status,
provider_user_id); provider_user_id);
if (NULL != attributes)
json_dumpf (attributes,
stderr,
JSON_INDENT (2));
MHD_resume_connection (rs->rc->connection); MHD_resume_connection (rs->rc->connection);
TALER_MHD_daemon_trigger (); TALER_MHD_daemon_trigger ();
rs->rc->response = response; rs->rc->response = response;

View File

@ -21,6 +21,7 @@ libtalerexchange_la_LDFLAGS = \
-version-info 5:0:0 \ -version-info 5:0:0 \
-no-undefined -no-undefined
libtalerexchange_la_SOURCES = \ libtalerexchange_la_SOURCES = \
exchange_api_add_aml_decision.c \
exchange_api_auditor_add_denomination.c \ exchange_api_auditor_add_denomination.c \
exchange_api_batch_deposit.c \ exchange_api_batch_deposit.c \
exchange_api_batch_withdraw.c \ exchange_api_batch_withdraw.c \
@ -37,6 +38,7 @@ libtalerexchange_la_SOURCES = \
exchange_api_kyc_proof.c \ exchange_api_kyc_proof.c \
exchange_api_kyc_wallet.c \ exchange_api_kyc_wallet.c \
exchange_api_link.c \ exchange_api_link.c \
exchange_api_management_add_partner.c \
exchange_api_management_auditor_disable.c \ exchange_api_management_auditor_disable.c \
exchange_api_management_auditor_enable.c \ exchange_api_management_auditor_enable.c \
exchange_api_management_drain_profits.c \ exchange_api_management_drain_profits.c \
@ -47,6 +49,7 @@ libtalerexchange_la_SOURCES = \
exchange_api_management_revoke_signing_key.c \ exchange_api_management_revoke_signing_key.c \
exchange_api_management_set_global_fee.c \ exchange_api_management_set_global_fee.c \
exchange_api_management_set_wire_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_disable.c \
exchange_api_management_wire_enable.c \ exchange_api_management_wire_enable.c \
exchange_api_melt.c \ exchange_api_melt.c \

View File

@ -136,7 +136,7 @@ TALER_EXCHANGE_add_aml_decision (
TALER_EXCHANGE_AddAmlDecisionCallback cb, TALER_EXCHANGE_AddAmlDecisionCallback cb,
void *cb_cls) void *cb_cls)
{ {
struct TALER_AmlOfficerPrivateKeyP officer_pub; struct TALER_AmlOfficerPublicKeyP officer_pub;
struct TALER_AmlOfficerSignatureP officer_sig; struct TALER_AmlOfficerSignatureP officer_sig;
struct TALER_EXCHANGE_AddAmlDecision *wh; struct TALER_EXCHANGE_AddAmlDecision *wh;
CURL *eh; CURL *eh;
@ -146,6 +146,7 @@ TALER_EXCHANGE_add_aml_decision (
&officer_pub.eddsa_pub); &officer_pub.eddsa_pub);
TALER_officer_aml_decision_sign (justification, TALER_officer_aml_decision_sign (justification,
decision_time, decision_time,
new_threshold,
h_payto, h_payto,
new_state, new_state,
officer_priv, officer_priv,
@ -187,8 +188,8 @@ TALER_EXCHANGE_add_aml_decision (
&officer_sig), &officer_sig),
GNUNET_JSON_pack_data_auto ("h_payto", GNUNET_JSON_pack_data_auto ("h_payto",
h_payto), h_payto),
GNUNET_JSON_pack_data_uint64 ("state", GNUNET_JSON_pack_uint64 ("state",
(uint32_t) new_state), (uint32_t) new_state),
TALER_JSON_pack_amount ("new_threshold", TALER_JSON_pack_amount ("new_threshold",
new_threshold), new_threshold),
GNUNET_JSON_pack_timestamp ("decision_time", GNUNET_JSON_pack_timestamp ("decision_time",

View File

@ -66,7 +66,7 @@ struct TALER_EXCHANGE_ManagementAddPartner
/** /**
* Function called when we're done processing the * 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 cls the `struct TALER_EXCHANGE_ManagementAddPartner *`
* @param response_code HTTP response code, 0 on error * @param response_code HTTP response code, 0 on error
@ -145,25 +145,9 @@ TALER_EXCHANGE_management_add_partner (
wh->cb = cb; wh->cb = cb;
wh->cb_cls = cb_cls; wh->cb_cls = cb_cls;
wh->ctx = ctx; wh->ctx = ctx;
{ wh->url = TALER_url_join (url,
char *path; "management/partners",
char opus[sizeof (*partner_pub) * 2]; NULL);
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);
}
if (NULL == wh->url) if (NULL == wh->url)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@ -180,6 +164,8 @@ TALER_EXCHANGE_management_add_partner (
end_date), end_date),
GNUNET_JSON_pack_time_rel ("wad_frequency", GNUNET_JSON_pack_time_rel ("wad_frequency",
wad_frequency), wad_frequency),
GNUNET_JSON_pack_data_auto ("partner_pub",
&partner_pub),
GNUNET_JSON_pack_data_auto ("master_sig", GNUNET_JSON_pack_data_auto ("master_sig",
&master_sig), &master_sig),
TALER_JSON_pack_amount ("wad_fee", TALER_JSON_pack_amount ("wad_fee",

View File

@ -144,25 +144,9 @@ TALER_EXCHANGE_management_update_aml_officer (
wh->cb = cb; wh->cb = cb;
wh->cb_cls = cb_cls; wh->cb_cls = cb_cls;
wh->ctx = ctx; wh->ctx = ctx;
{ wh->url = TALER_url_join (url,
char *path; "management/aml-officers",
char opus[sizeof (*officer_pub) * 2]; NULL);
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);
}
if (NULL == wh->url) if (NULL == wh->url)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@ -173,12 +157,14 @@ TALER_EXCHANGE_management_update_aml_officer (
body = GNUNET_JSON_PACK ( body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("officer_name", GNUNET_JSON_pack_string ("officer_name",
officer_name), officer_name),
GNUNET_JSON_pack_data_auto ("officer_pub",
officer_pub),
GNUNET_JSON_pack_data_auto ("master_sig", GNUNET_JSON_pack_data_auto ("master_sig",
master_sig), master_sig),
GNUNET_JSON_pack_data_bool ("is_active", GNUNET_JSON_pack_bool ("is_active",
is_active), is_active),
GNUNET_JSON_pack_data_bool ("read_only", GNUNET_JSON_pack_bool ("read_only",
read_only), read_only),
GNUNET_JSON_pack_timestamp ("change_date", GNUNET_JSON_pack_timestamp ("change_date",
change_date)); change_date));
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);