Compare commits

..

1 Commits

Author SHA1 Message Date
e660a513fd
-fix url to gana submodule 2023-01-19 21:07:42 +01:00
398 changed files with 8068 additions and 23763 deletions

View File

@ -17,7 +17,7 @@
#
#
AC_PREREQ([2.69])
AC_INIT([taler-exchange],[0.9.2],[taler-bug@gnunet.org])
AC_INIT([taler-exchange],[0.9.1],[taler-bug@gnunet.org])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_SRCDIR([src/util/util.c])
AC_CONFIG_HEADERS([taler_config.h])

@ -1 +1 @@
Subproject commit bd4e73b2ed06269fdee42eaad21acb5be8be9302
Subproject commit 832685b6a942a6ebbec8e1e5b8c33b6b85b0a727

View File

@ -46,12 +46,6 @@
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
</member>
<member kind="define">
<type>#define</type>
<name>MHD_HTTP_CONTENT_TOO_LARGE</name>
<anchorfile>microhttpd.h</anchorfile>
<arglist></arglist>
</member>
<member kind="define">
<type>#define</type>
<name>MHD_HTTP_REQUEST_TIMEOUT</name>

24
debian/changelog vendored
View File

@ -1,27 +1,3 @@
taler-exchange (0.9.2-3) unstable; urgency=low
* Improvements to timeout handling when DB is not available yet.
-- Florian Dold <dold@taler.net> Tue, 14 Mar 2023 12:30:15 +0100
taler-exchange (0.9.2-2) unstable; urgency=low
* Further improvements to Debian package.
-- Christian Grothoff <grothoff@gnu.org> Sat, 3 Mar 2023 23:50:12 +0200
taler-exchange (0.9.2-1) unstable; urgency=low
* Minor improvements to Debian package, also adds age-withdraw REST APIs.
-- Christian Grothoff <grothoff@gnu.org> Sat, 3 Mar 2023 13:50:12 +0200
taler-exchange (0.9.2) unstable; urgency=low
* Packaging latest release.
-- Christian Grothoff <grothoff@gnu.org> Tue, 21 Feb 2023 13:50:12 +0200
taler-exchange (0.9.1) unstable; urgency=low
* Packaging latest release.

View File

@ -30,8 +30,6 @@
# systems is always rounded to this unit.
#currency_round_unit = KUDOS:0.01
# Monthly amount that mandatorily triggers an AML check
#AML_THRESHOLD = KUDOS:10000000
[paths]

View File

@ -1,18 +1,7 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
access_log /var/log/nginx/auditor.log;
error_log /var/log/nginx/auditor.err;
location /taler-auditor/ {
proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host "localhost";
#proxy_set_header X-Forwarded-Proto "https";
}
location /taler-auditor/ {
proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host "example.com";
proxy_set_header X-Forwarded-Proto "https";
}

View File

@ -2,16 +2,13 @@ server {
listen 80;
listen [::]:80;
server_name localhost;
access_log /var/log/nginx/exchange.log;
error_log /var/log/nginx/exchange.err;
#server_name example.com;
location /taler-exchange/ {
proxy_pass http://unix:/run/taler/exchange-httpd/exchange-http.sock:/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host "localhost";
#proxy_set_header X-Forwarded-Host "example.com";
#proxy_set_header X-Forwarded-Proto "https";
}
}

View File

@ -6,11 +6,11 @@
# which you can get using `taler-exchange-offline setup`.
# This is just an example, your key will be different!
# MASTER_PUBLIC_KEY = YE6Q6TR1EDB7FD0S68TGDZGF1P0GHJD2S0XVV8R2S62MYJ6HJ4ZG
# MASTER_PUBLIC_KEY =
MASTER_PUBLIC_KEY =
# Publicly visible base URL of the exchange.
# BASE_URL = https://example.com/
# BASE_URL =
BASE_URL =
# For your terms of service and privacy policy, you should specify
# an Etag that must be updated whenever there are significant
@ -20,14 +20,12 @@
# TERMS_ETAG =
# PRIVACY_ETAG =
SERVE = unix
UNIXPATH_MODE = 666
# Bank accounts used by the exchange should be specified here:
[exchange-account-1]
enable_credit = no
enable_debit = no
enable_credit = yes
enable_debit = yes
# Account identifier in the form of an RFC-8905 payto:// URI.
# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
@ -36,4 +34,4 @@ payto_uri =
# Credentials to access the account are in a separate
# config file with restricted permissions.
@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf
@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials.secret.conf

View File

@ -4,7 +4,7 @@
# Typically, there should only be a single line here, of the form:
# CONFIG=postgres:///DATABASE
CONFIG=postgres:///DATABASE
# The details of the URI depend on where the database lives and how
# access control was configured.

View File

@ -5,5 +5,6 @@ usr/share/taler/config.d/paths.conf
usr/share/taler/config.d/taler.conf
debian/etc-libtalerexchange/* etc/
usr/bin/taler-config
usr/bin/taler-crypto-worker
usr/share/man/man5/taler.conf.5
usr/share/man/man1/taler-config*

View File

@ -4,21 +4,20 @@ set -e
. /usr/share/debconf/confmodule
TALER_HOME="/var/lib/taler"
case "${1}" in
configure)
if ! getent group taler-exchange-offline >/dev/null; then
addgroup --quiet taler-exchange-offline
addgroup --quiet --system taler-exchange-offline
fi
if ! getent passwd taler-exchange-offline >/dev/null; then
adduser --quiet \
--disabled-password \
--system \
--shell /bin/bash \
--home /home/taler-exchange-offline \
adduser --quiet --system \
--ingroup taler-exchange-offline \
taler-exchange-offline
--no-create-home \
--home ${TALER_HOME} taler-exchange-offline
fi
;;

View File

@ -30,7 +30,6 @@ configure)
if ! getent passwd ${_EUSERNAME} >/dev/null; then
adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_EUSERNAME}
adduser --quiet ${_EUSERNAME} ${_DBGROUPNAME}
adduser --quiet ${_EUSERNAME} ${_GROUPNAME}
fi
if ! getent passwd ${_RSECUSERNAME} >/dev/null; then
adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_RSECUSERNAME}
@ -54,10 +53,10 @@ configure)
adduser --quiet ${_AGGRUSERNAME} ${_DBGROUPNAME}
fi
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials-1.secret.conf >/dev/null 2>&1; then
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials.secret.conf >/dev/null 2>&1; then
dpkg-statoverride --add --update \
${_WIREUSERNAME} root 460 \
/etc/taler/secrets/exchange-accountcredentials-1.secret.conf
/etc/taler/secrets/exchange-accountcredentials.secret.conf
fi
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-db.secret.conf >/dev/null 2>&1; then

View File

@ -7,7 +7,7 @@ After=postgres.service
User=taler-exchange-aggregator
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -15,4 +15,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -6,7 +6,7 @@ PartOf=taler-exchange.target
User=taler-exchange-aggregator
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -14,4 +14,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -7,7 +7,7 @@ After=network.target postgres.service
User=taler-exchange-closer
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -15,4 +15,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -7,7 +7,7 @@ After=postgres.service
User=taler-exchange-expire
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -15,4 +15,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -8,19 +8,11 @@ PartOf=taler-exchange.target
[Service]
User=taler-exchange-httpd
Type=simple
# Depending on the configuration, the service process kills itself and then
# needs to be restarted. Thus no significant delay on restarts.
# Depending on the configuration, the service suicides and then
# needs to be restarted.
Restart=always
# Do not dally on restarts.
RestartSec=1ms
# Disable the service if more than 5 restarts are encountered within 5s.
# These are usually the systemd defaults, but can be overwritten, thus we set
# them here explicitly, as the exchange code assumes StartLimitInterval
# to be >=5s.
StartLimitBurst=5
StartLimitInterval=5s
ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal

View File

@ -7,7 +7,7 @@ PartOf=taler-exchange.target
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -15,4 +15,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -7,8 +7,7 @@ PartOf=taler-exchange.target
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
RuntimeMaxSec=3600s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal

View File

@ -7,7 +7,7 @@ PartOf=taler-exchange.target
User=taler-exchange-wire
Type=simple
Restart=always
RestartSec=1s
RestartSec=100ms
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
StandardOutput=journal
StandardError=journal
@ -15,4 +15,3 @@ PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
Slice=taler-exchange.slice
RuntimeMaxSec=3600s

View File

@ -1,8 +1,7 @@
#Type Path Mode UID GID Age Argument
d /run/taler/exchange-secmod-rsa 0755 taler-exchange-secmod-rsa taler-exchange-secmod - -
d /run/taler/exchange-secmod-cs 0755 taler-exchange-secmod-cs taler-exchange-secmod - -
d /run/taler/exchange-secmod-eddsa 0755 taler-exchange-secmod-eddsa taler-exchange-secmod - -
d /run/taler/exchange-httpd 0750 taler-exchange-httpd www-data - -
d /var/lib/taler/exchange-secmod-cs 0700 taler-exchange-secmod-cs taler-exchange-secmod - -
d /var/lib/taler/exchange-offline 0700 taler-exchange-offline taler-exchange-offline - -
d /var/lib/taler/exchange-secmod-rsa 0700 taler-exchange-secmod-rsa taler-exchange-secmod - -
d /var/lib/taler/exchange-secmod-eddsa 0700 taler-exchange-secmod-eddsa taler-exchange-secmod - -

View File

@ -193,7 +193,7 @@ echo " DONE"
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://x-taler-bank/localhost/43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
echo " DONE"
@ -214,7 +214,7 @@ bash
# {
# amountToSpend: "TESTKUDOS:4",
# amountToWithdraw: "TESTKUDOS:10",
# bankAccessApiBaseUrl: $BANK_URL,
# bankBaseUrl: $BANK_URL,
# exchangeBaseUrl: $EXCHANGE_URL,
# merchantBaseUrl: $MERCHANT_URL,
# }' \

View File

@ -141,7 +141,6 @@ CONFIG = /research/taler/exchange/src/auditor/auditor-basedb.conf
[taler]
CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
CURRENCY = TESTKUDOS
AML_THRESHOLD = TESTKUDOS:1000000
[merchantdb-postgres]
CONFIG = postgres:///auditor-basedb

View File

@ -398,7 +398,7 @@ echo " DONE"
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
echo " DONE"
@ -411,7 +411,7 @@ taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'runI
{
amountToSpend: "TESTKUDOS:4",
amountToWithdraw: "TESTKUDOS:10",
bankAccessApiBaseUrl: $BANK_URL,
bankBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL,
merchantBaseUrl: $MERCHANT_URL,
}' \

View File

@ -400,7 +400,7 @@ echo " DONE"
# Setup merchant
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "accounts":[{"payto_uri":"payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"}],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "payto_uris":["payto://iban/SANDBOXX/DE474361?receiver-name=Merchant43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' http://localhost:9966/management/instances
# run wallet CLI
@ -410,7 +410,7 @@ taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api --expect-success 'with
"$(jq -n '
{
amount: "TESTKUDOS:8",
bankAccessApiBaseUrl: $BANK_URL,
bankBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL,
}' \
--arg BANK_URL "$BANK_URL/demobanks/default/access-api/" \

View File

@ -271,9 +271,7 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
const char *upload_data,
size_t *upload_data_size)
{
struct TALER_AUDITORDB_DepositConfirmation dc = {
.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS
};
struct TALER_AUDITORDB_DepositConfirmation dc;
struct TALER_AUDITORDB_ExchangeSigningKey es;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
@ -284,10 +282,8 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
&dc.h_wire),
GNUNET_JSON_spec_timestamp ("exchange_timestamp",
&dc.exchange_timestamp),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
&dc.refund_deadline),
NULL),
GNUNET_JSON_spec_timestamp ("refund_deadline",
&dc.refund_deadline),
GNUNET_JSON_spec_timestamp ("wire_deadline",
&dc.wire_deadline),
TALER_JSON_spec_amount ("amount_without_fee",

View File

@ -885,9 +885,8 @@ handle_purse_decision (
report_row_inconsistency ("purse-request",
rowid,
"purse fee higher than balance");
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
&balance_without_purse_fee));
TALER_amount_set_zero (TALER_ARL_currency,
&balance_without_purse_fee);
}
if (refunded)
@ -1022,9 +1021,8 @@ verify_purse_balance (void *cls,
report_row_inconsistency ("purse",
0,
"purse fee higher than balance");
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
&balance_without_purse_fee));
TALER_amount_set_zero (TALER_ARL_currency,
&balance_without_purse_fee);
}
if (0 != TALER_amount_cmp (&ps->exchange_balance,

View File

@ -1081,9 +1081,8 @@ handle_reserve_closed (
}
if (NULL == payto_uri)
{
if ( (NULL == rs->sender_account) ||
(0 != strcmp (rs->sender_account,
receiver_account)) )
if (0 != strcmp (rs->sender_account,
receiver_account))
{
report_row_inconsistency ("reserves_close",
rowid,
@ -1111,8 +1110,8 @@ handle_reserve_closed (
rowid,
"target account not verified, auditor does not know reserve");
}
else if (0 != strcmp (rs->sender_account,
receiver_account))
if (0 != strcmp (rs->sender_account,
receiver_account))
{
report_row_inconsistency ("reserves_close",
rowid,

View File

@ -96,7 +96,7 @@ function cleanup()
function exit_cleanup()
{
echo "Running exit-cleanup"
if test ! -z "${POSTGRES_PATH:-}"
if test -z "${POSTGRES_PATH:-}"
then
echo "Stopping Postgres at ${POSTGRES_PATH}"
${POSTGRES_PATH}/pg_ctl -D $TMPDIR -l /dev/null stop &> /dev/null || true

View File

@ -248,15 +248,12 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
{
if ( (0 < num_results) &&
(! GNUNET_TIME_relative_is_zero (timeout)) )
/* 0 == start_row is implied, go with timeout into future */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&long_poll_ms=%llu",
(long long) num_results,
tms);
else
/* Going back from current transaction or have no timeout;
hence timeout makes no sense */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld",
@ -266,7 +263,6 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
{
if ( (0 < num_results) &&
(! GNUNET_TIME_relative_is_zero (timeout)) )
/* going forward from num_result */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&start=%llu&long_poll_ms=%llu",
@ -274,8 +270,6 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
(unsigned long long) start_row,
tms);
else
/* going backwards or have no timeout;
hence timeout makes no sense */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&start=%llu",

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ DB = postgres
# exchange (or the twister) is actually listening.
base_url = "http://localhost:8081/"
WIREWATCH_IDLE_SLEEP_INTERVAL = 500 ms
WIREWATCH_IDLE_SLEEP_INTERVAL = 1500 ms
[exchange-offline]
MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
@ -51,11 +51,11 @@ MAX_DEBT = EUR:100000000000.0
MAX_DEBT_BANK = EUR:1000000000000000.0
[benchmark]
USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42?receiver-name=user42
USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42
[exchange-account-2]
# What is the payto://-URL of the exchange (to generate wire response)
PAYTO_URI = "payto://x-taler-bank/localhost:8082/Exchange?receiver-name=Exchange"
PAYTO_URI = "payto://x-taler-bank/localhost:8082/Exchange"
enable_debit = YES
enable_credit = YES

View File

@ -689,8 +689,6 @@ parallel_benchmark (void)
}
/* But be extra sure we did finish all shards by doing one more */
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Shard check phase\n");
wirewatch[0] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-wirewatch",

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
(C) 2014-2023 Taler Systems SA
(C) 2014-2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as

View File

@ -15,7 +15,8 @@ endif
bin_PROGRAMS = \
taler-auditor-offline \
taler-exchange-offline \
taler-exchange-dbinit
taler-exchange-dbinit \
taler-crypto-worker
taler_exchange_offline_SOURCES = \
taler-exchange-offline.c
@ -59,6 +60,19 @@ taler_exchange_dbinit_CPPFLAGS = \
-I$(top_srcdir)/src/pq/ \
$(POSTGRESQL_CPPFLAGS)
taler_crypto_worker_SOURCES = \
taler-crypto-worker.c
taler_crypto_worker_LDADD = \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/json/libtalerjson.la \
-lgnunetutil \
-lgnunetjson \
-ljansson \
$(LIBGCRYPT_LIBS) \
$(XLIB)
# Testcases

View File

@ -0,0 +1,459 @@
/*
This file is part of TALER
Copyright (C) 2014-2021 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/>
*/
/**
* @file exchange-tools/taler-crypto-worker.c
* @brief Standalone process to perform various cryptographic operations.
* @author Florian Dold
*/
#include "platform.h"
#include "taler_util.h"
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include "taler_error_codes.h"
#include "taler_json_lib.h"
#include "taler_signatures.h"
/**
* Return value from main().
*/
static int global_ret;
/**
* Main function that will be run under the GNUnet scheduler.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param cfg configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
(void) cls;
(void) args;
(void) cfgfile;
(void) cfg;
json_t *req;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"started crypto worker\n");
for (;;)
{
const char *op;
const json_t *args;
req = json_loadf (stdin, JSON_DISABLE_EOF_CHECK, NULL);
if (NULL == req)
{
if (feof (stdin))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"end of input\n");
global_ret = 0;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"invalid JSON\n");
global_ret = 1;
return;
}
op = json_string_value (json_object_get (req,
"op"));
if (! op)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"no op specified\n");
global_ret = 1;
return;
}
args = json_object_get (req, "args");
if (! args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"no args specified\n");
global_ret = 1;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"got request\n");
if (0 == strcmp ("eddsa_get_public",
op))
{
struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
json_t *resp;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("eddsa_priv",
&eddsa_priv),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != GNUNET_JSON_parse (args,
spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
GNUNET_CRYPTO_eddsa_key_get_public (&eddsa_priv,
&eddsa_pub);
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("eddsa_pub",
&eddsa_pub)
);
json_dumpf (resp, stdout, JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (spec);
continue;
}
if (0 == strcmp ("ecdhe_get_public",
op))
{
struct GNUNET_CRYPTO_EcdhePublicKey ecdhe_pub;
struct GNUNET_CRYPTO_EcdhePrivateKey ecdhe_priv;
json_t *resp;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("ecdhe_priv",
&ecdhe_priv),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != GNUNET_JSON_parse (args,
spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
GNUNET_CRYPTO_ecdhe_key_get_public (&ecdhe_priv,
&ecdhe_pub);
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("ecdhe_pub",
&ecdhe_pub)
);
json_dumpf (resp, stdout, JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (spec);
continue;
}
if (0 == strcmp ("eddsa_verify",
op))
{
struct GNUNET_CRYPTO_EddsaPublicKey pub;
struct GNUNET_CRYPTO_EddsaSignature sig;
struct GNUNET_CRYPTO_EccSignaturePurpose *msg;
size_t msg_size;
enum GNUNET_GenericReturnValue verify_ret;
json_t *resp;
struct GNUNET_JSON_Specification eddsa_verify_spec[] = {
GNUNET_JSON_spec_fixed_auto ("pub",
&pub),
GNUNET_JSON_spec_fixed_auto ("sig",
&sig),
GNUNET_JSON_spec_varsize ("msg",
(void **) &msg,
&msg_size),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != GNUNET_JSON_parse (args,
eddsa_verify_spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
verify_ret = GNUNET_CRYPTO_eddsa_verify_ (
ntohl (msg->purpose),
msg,
&sig,
&pub);
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_bool ("valid",
GNUNET_OK == verify_ret));
json_dumpf (resp, stdout, JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (eddsa_verify_spec);
continue;
}
if (0 == strcmp ("kx_ecdhe_eddsa",
op))
{
struct GNUNET_CRYPTO_EcdhePrivateKey priv;
struct GNUNET_CRYPTO_EddsaPublicKey pub;
struct GNUNET_HashCode key_material;
json_t *resp;
struct GNUNET_JSON_Specification kx_spec[] = {
GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
&pub),
GNUNET_JSON_spec_fixed_auto ("ecdhe_priv",
&priv),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (args,
kx_spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
GNUNET_assert (GNUNET_OK ==
GNUNET_CRYPTO_ecdh_eddsa (&priv,
&pub,
&key_material));
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h",
&key_material)
);
json_dumpf (resp,
stdout,
JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (kx_spec);
continue;
}
if (0 == strcmp ("eddsa_sign",
op))
{
struct GNUNET_CRYPTO_EddsaSignature sig;
struct GNUNET_CRYPTO_EccSignaturePurpose *msg;
struct GNUNET_CRYPTO_EddsaPrivateKey priv;
size_t msg_size;
json_t *resp;
struct GNUNET_JSON_Specification eddsa_sign_spec[] = {
GNUNET_JSON_spec_fixed_auto ("priv",
&priv),
GNUNET_JSON_spec_varsize ("msg",
(void **) &msg,
&msg_size),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (args,
eddsa_sign_spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
GNUNET_assert (GNUNET_OK ==
GNUNET_CRYPTO_eddsa_sign_ (
&priv,
msg,
&sig));
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("sig", &sig)
);
json_dumpf (resp, stdout,
JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (eddsa_sign_spec);
continue;
}
if (0 == strcmp ("setup_refresh_planchet", op))
{
struct TALER_TransferSecretP transfer_secret;
uint32_t coin_index;
json_t *resp;
struct GNUNET_JSON_Specification setup_refresh_planchet_spec[] = {
GNUNET_JSON_spec_uint32 ("coin_index",
&coin_index),
GNUNET_JSON_spec_fixed_auto ("transfer_secret",
&transfer_secret),
GNUNET_JSON_spec_end ()
};
struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_CoinSpendPrivateKeyP coin_priv;
struct TALER_PlanchetMasterSecretP ps;
struct TALER_ExchangeWithdrawValues alg_values = {
// FIXME: also allow CS
.cipher = TALER_DENOMINATION_RSA,
};
union TALER_DenominationBlindingKeyP dbk;
if (GNUNET_OK !=
GNUNET_JSON_parse (args,
setup_refresh_planchet_spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
TALER_transfer_secret_to_planchet_secret (&transfer_secret,
coin_index,
&ps);
TALER_planchet_setup_coin_priv (&ps,
&alg_values,
&coin_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
TALER_planchet_blinding_secret_create (&ps,
&alg_values,
&dbk);
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("coin_priv", &coin_priv),
GNUNET_JSON_pack_data_auto ("coin_pub", &coin_pub),
GNUNET_JSON_pack_data_auto ("blinding_key", &dbk.rsa_bks)
);
json_dumpf (resp, stdout, JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (setup_refresh_planchet_spec);
continue;
}
if (0 == strcmp ("rsa_blind", op))
{
struct GNUNET_HashCode hm;
struct GNUNET_CRYPTO_RsaBlindingKeySecret bks;
void *pub_enc;
size_t pub_enc_size;
int success;
struct GNUNET_CRYPTO_RsaPublicKey *pub;
void *blinded_buf;
size_t blinded_size;
json_t *resp;
struct GNUNET_JSON_Specification rsa_blind_spec[] = {
GNUNET_JSON_spec_fixed_auto ("hm",
&hm),
GNUNET_JSON_spec_fixed_auto ("bks",
&bks),
GNUNET_JSON_spec_varsize ("pub",
&pub_enc,
&pub_enc_size),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (args,
rsa_blind_spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"malformed op args\n");
global_ret = 1;
return;
}
pub = GNUNET_CRYPTO_rsa_public_key_decode (pub_enc,
pub_enc_size);
success = GNUNET_CRYPTO_rsa_blind (&hm,
&bks,
pub,
&blinded_buf,
&blinded_size);
if (GNUNET_YES == success)
{
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_varsize ("blinded", blinded_buf, blinded_size),
GNUNET_JSON_pack_bool ("success", true)
);
}
else
{
resp = GNUNET_JSON_PACK (
GNUNET_JSON_pack_bool ("success", false)
);
}
json_dumpf (resp, stdout, JSON_COMPACT);
printf ("\n");
fflush (stdout);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"sent response\n");
GNUNET_JSON_parse_free (rsa_blind_spec);
GNUNET_free (blinded_buf);
continue;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"unsupported operation '%s'\n",
op);
global_ret = 1;
return;
}
}
/**
* The entry point.
*
* @param argc number of arguments in @a argv
* @param argv command-line arguments
* @return 0 on normal termination
*/
int
main (int argc,
char **argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_OPTION_END
};
int ret;
/* force linker to link against libtalerutil; if we do
not do this, the linker may "optimize" libtalerutil
away and skip #TALER_OS_init(), which we do need */
TALER_OS_init ();
ret = GNUNET_PROGRAM_run (argc, argv,
"taler-crypto-worker",
"Execute cryptographic operations read from stdin",
options,
&run,
NULL);
if (GNUNET_NO == ret)
return 0;
if (GNUNET_SYSERR == ret)
return 1;
return global_ret;
}

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2020-2023 Taler Systems SA
Copyright (C) 2020, 2021, 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
@ -112,16 +112,6 @@
*/
#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().
*/
@ -508,62 +498,6 @@ 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.
*/
@ -574,27 +508,6 @@ static struct GNUNET_SCHEDULER_Task *nxt;
*/
static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh;
/**
* Active AML staff change requests.
*/
static struct AmlStaffRequest *asr_head;
/**
* Active AML staff change requests.
*/
static struct AmlStaffRequest *asr_tail;
/**
* Active partner add requests.
*/
static struct PartnerAddRequest *par_head;
/**
* Active partner add requests.
*/
static struct PartnerAddRequest *par_tail;
/**
* Active denomiantion revocation requests.
*/
@ -716,36 +629,6 @@ do_shutdown (void *cls)
{
(void) cls;
{
struct AmlStaffRequest *asr;
while (NULL != (asr = asr_head))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Aborting incomplete AML staff update #%u\n",
(unsigned int) asr->idx);
TALER_EXCHANGE_management_update_aml_officer_cancel (asr->h);
GNUNET_CONTAINER_DLL_remove (asr_head,
asr_tail,
asr);
GNUNET_free (asr);
}
}
{
struct PartnerAddRequest *par;
while (NULL != (par = par_head))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Aborting incomplete partner add request #%u\n",
(unsigned int) par->idx);
TALER_EXCHANGE_management_add_partner_cancel (par->h);
GNUNET_CONTAINER_DLL_remove (par_head,
par_tail,
par);
GNUNET_free (par);
}
}
{
struct DenomRevocationRequest *drr;
@ -959,8 +842,6 @@ static void
test_shutdown (void)
{
if ( (NULL == drr_head) &&
(NULL == par_head) &&
(NULL == asr_head) &&
(NULL == srr_head) &&
(NULL == aar_head) &&
(NULL == adr_head) &&
@ -2333,221 +2214,6 @@ 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.
*
@ -2601,14 +2267,6 @@ trigger_upload (const char *exchange_url)
.key = OP_EXTENSIONS,
.cb = &upload_extensions
},
{
.key = OP_UPDATE_AML_STAFF,
.cb = &update_aml_staff
},
{
.key = OP_ADD_PARTNER,
.cb = &add_partner
},
/* array termination */
{
.key = NULL
@ -3382,261 +3040,6 @@ do_drain (char *const *args)
}
/**
* Add partner.
*
* @param args the array of command-line arguments to process next;
* args[0] must be the partner's master public key, args[1] the partner's
* API base URL, args[2] the wad fee, args[3] the wad frequency, and
* args[4] the year (including possibly 'now')
*/
static void
do_add_partner (char *const *args)
{
struct TALER_MasterPublicKeyP partner_pub;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct GNUNET_TIME_Relative wad_frequency;
struct TALER_Amount wad_fee;
const char *partner_base_url;
struct TALER_MasterSignatureP master_sig;
char dummy;
unsigned int year;
if (NULL != in)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not adding partner\n");
test_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
if ( (NULL == args[0]) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&partner_pub,
sizeof (partner_pub))) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify the partner master public key as first argument for this subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (NULL == args[1]) ||
(0 != strncmp ("http",
args[1],
strlen ("http"))) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify the partner's base URL as the 2nd argument to this subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
partner_base_url = args[1];
if ( (NULL == args[2]) ||
(GNUNET_OK !=
TALER_string_to_amount (args[2],
&wad_fee)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid amount `%s' specified for wad fee of partner\n",
args[2]);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (NULL == args[3]) ||
(GNUNET_OK !=
GNUNET_STRINGS_fancy_time_to_relative (args[3],
&wad_frequency)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid wad frequency `%s' specified for add partner\n",
args[3]);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if ( (NULL == args[4]) ||
( (1 != sscanf (args[4],
"%u%c",
&year,
&dummy)) &&
(0 != strcasecmp ("now",
args[4])) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid year `%s' specified for add partner\n",
args[4]);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if (0 == strcasecmp ("now",
args[4]))
year = GNUNET_TIME_get_current_year ();
start_date = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_year_to_time (year));
end_date = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_year_to_time (year + 1));
if (GNUNET_OK !=
load_offline_key (GNUNET_NO))
return;
TALER_exchange_offline_partner_details_sign (&partner_pub,
start_date,
end_date,
wad_frequency,
&wad_fee,
partner_base_url,
&master_priv,
&master_sig);
output_operation (OP_ADD_PARTNER,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("partner_base_url",
partner_base_url),
GNUNET_JSON_pack_time_rel ("wad_frequency",
wad_frequency),
GNUNET_JSON_pack_timestamp ("start_date",
start_date),
GNUNET_JSON_pack_timestamp ("end_date",
end_date),
GNUNET_JSON_pack_data_auto ("partner_pub",
&partner_pub),
GNUNET_JSON_pack_data_auto ("master_sig",
&master_sig)));
next (args + 5);
}
/**
* Enable or disable AML staff.
*
* @param is_active true to enable, false to disable
* @param args the array of command-line arguments to process next; args[0] must be the AML staff's public key, args[1] the AML staff's legal name, and if @a is_active then args[2] rw (read write) or ro (read only)
*/
static void
do_set_aml_staff (bool is_active,
char *const *args)
{
struct TALER_AmlOfficerPublicKeyP officer_pub;
const char *officer_name;
bool read_only;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_TIME_Timestamp now
= GNUNET_TIME_timestamp_get ();
if (NULL != in)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not updating AML staff status\n");
test_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
if ( (NULL == args[0]) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&officer_pub,
sizeof (officer_pub))) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify the AML officer's public key as first argument for this subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if (NULL == args[1])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify the officer's legal name as the 2nd argument to this subcommand\n");
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
officer_name = args[1];
if (is_active)
{
if ( (NULL == args[2]) ||
( (0 != strcmp (args[2],
"ro")) &&
(0 != strcmp (args[2],
"rw")) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify 'ro' or 'rw' (and not `%s') for the access level\n",
args[2]);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
read_only = (0 == strcmp (args[2],
"ro"));
}
else
{
read_only = true;
}
if (GNUNET_OK !=
load_offline_key (GNUNET_NO))
return;
TALER_exchange_offline_aml_officer_status_sign (&officer_pub,
officer_name,
now,
is_active,
read_only,
&master_priv,
&master_sig);
output_operation (OP_UPDATE_AML_STAFF,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("officer_name",
officer_name),
GNUNET_JSON_pack_timestamp ("change_date",
now),
GNUNET_JSON_pack_bool ("is_active",
is_active),
GNUNET_JSON_pack_bool ("read_only",
read_only),
GNUNET_JSON_pack_data_auto ("officer_pub",
&officer_pub),
GNUNET_JSON_pack_data_auto ("master_sig",
&master_sig)));
next (args + (is_active ? 3 : 2));
}
/**
* Disable AML staff.
*
* @param args the array of command-line arguments to process next;
* args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only)
*/
static void
disable_aml_staff (char *const *args)
{
do_set_aml_staff (false,
args);
}
/**
* Enable AML staff.
*
* @param args the array of command-line arguments to process next;
* args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only)
*/
static void
enable_aml_staff (char *const *args)
{
do_set_aml_staff (true,
args);
}
/**
* Function called with information about future keys. Dumps the JSON output
* (on success), either into an internal buffer or to stdout (depending on
@ -5072,24 +4475,6 @@ work (void *cls)
"drain profits from exchange escrow account to regular exchange operator account (amount, debit account configuration section and credit account payto://-URI must be given as arguments)",
.cb = &do_drain
},
{
.name = "add-partner",
.help =
"add partner exchange for P2P wad transfers (partner master public key, partner base URL, wad fee, wad frequency and validity year must be given as arguments)",
.cb = &do_add_partner
},
{
.name = "aml-enable",
.help =
"enable AML staff member (staff member public key, legal name and rw (read write) or ro (read only) must be given as arguments)",
.cb = &enable_aml_staff
},
{
.name = "aml-disable",
.help =
"disable AML staff member (staff member public key and legal name must be given as arguments)",
.cb = &disable_aml_staff
},
{
.name = "upload",
.help =

View File

@ -123,15 +123,9 @@ taler_exchange_wirewatch_LDADD = \
taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.h \
taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \
taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \
taler-exchange-httpd_aml-decision-get.c \
taler-exchange-httpd_aml-decisions-get.c \
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_age-withdraw.c taler-exchange-httpd_age-withdraw.h \
taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \
taler-exchange-httpd_db.c taler-exchange-httpd_db.h \
@ -145,14 +139,12 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
taler-exchange-httpd_management.h \
taler-exchange-httpd_management_aml-officers.c \
taler-exchange-httpd_management_auditors.c \
taler-exchange-httpd_management_auditors_AP_disable.c \
taler-exchange-httpd_management_denominations_HDP_revoke.c \
taler-exchange-httpd_management_drain.c \
taler-exchange-httpd_management_extensions.c \
taler-exchange-httpd_management_global_fees.c \
taler-exchange-httpd_management_partners.c \
taler-exchange-httpd_management_post_keys.c \
taler-exchange-httpd_management_signkey_EP_revoke.c \
taler-exchange-httpd_management_wire_enable.c \

View File

@ -6,10 +6,6 @@
# This must be adjusted to your actual installation.
# MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
# Attribute encryption key for storing attributes encrypted
# in the database. Should be a high-entropy nonce.
ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
# How long do we allow /keys to be cached at most? The actual
# limit is the minimum of this value and the first expected
# significant change in /keys based on the expiration times.
@ -19,7 +15,7 @@ MAX_KEYS_CACHING = forever
# After how many requests should the exchange auto-restart
# (to address potential issues with memory fragmentation)?
# If this option is not specified, auto-restarting is disabled.
# MAX_REQUESTS = 100000
# MAX_REQUESTS = 10000000
# How to access our database
DB = postgres

View File

@ -148,12 +148,6 @@ struct Shard
*/
static struct TALER_Amount currency_round_unit;
/**
* What is the largest amount we transfer before triggering
* an AML check?
*/
static struct TALER_Amount aml_threshold;
/**
* What is the base URL of this exchange? Used in the
* wire transfer subjects so that merchants and governments
@ -300,20 +294,11 @@ parse_aggregator_config (void)
"taler",
"CURRENCY_ROUND_UNIT",
&currency_round_unit)) ||
(TALER_amount_is_zero (&currency_round_unit)) )
( (0 != currency_round_unit.fraction) &&
(0 != currency_round_unit.value) ) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need non-zero amount in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_config_get_amount (cfg,
"taler",
"AML_THRESHOLD",
&aml_threshold))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need amount in section `TALER' under `AML_THRESHOLD'\n");
"Need non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
return GNUNET_SYSERR;
}
@ -496,22 +481,16 @@ return_relevant_amounts (void *cls,
static bool
kyc_satisfied (struct AggregationUnit *au_active)
{
char *requirement;
const char *requirement;
enum GNUNET_DB_QueryStatus qs;
qs = TALER_KYCLOGIC_kyc_test_required (
requirement = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
&au_active->h_payto,
db_plugin->select_satisfied_kyc_processes,
db_plugin->cls,
&return_relevant_amounts,
(void *) au_active,
&requirement);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return false;
}
(void *) au_active);
if (NULL == requirement)
return true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -535,114 +514,6 @@ kyc_satisfied (struct AggregationUnit *au_active)
"Legitimization process %llu started\n",
(unsigned long long) au_active->requirement_row);
}
GNUNET_free (requirement);
return false;
}
/**
* Function called on each @a amount that was found to
* be relevant for an AML check.
*
* @param cls closure with the `struct TALER_Amount *` where we store the sum
* @param amount encountered transaction amount
* @param date when was the amount encountered
* @return #GNUNET_OK to continue to iterate,
* #GNUNET_NO to abort iteration
* #GNUNET_SYSERR on internal error (also abort itaration)
*/
static enum GNUNET_GenericReturnValue
sum_for_aml (
void *cls,
const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute date)
{
struct TALER_Amount *sum = cls;
(void) date;
if (0 >
TALER_amount_add (sum,
sum,
amount))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Test if AML is required for a transfer to @a h_payto.
*
* @param[in,out] au_active aggregation unit to check for
* @return true if AML checks are satisfied
*/
static bool
aml_satisfied (struct AggregationUnit *au_active)
{
enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount total;
struct TALER_Amount threshold;
enum TALER_AmlDecisionState decision;
struct TALER_EXCHANGEDB_KycStatus kyc;
total = au_active->final_amount;
qs = db_plugin->select_aggregation_amounts_for_kyc_check (
db_plugin->cls,
&au_active->h_payto,
GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
GNUNET_TIME_UNIT_MONTHS),
&sum_for_aml,
&total);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return false;
}
qs = db_plugin->select_aml_threshold (db_plugin->cls,
&au_active->h_payto,
&decision,
&kyc,
&threshold);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return false;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
threshold = aml_threshold; /* use default */
decision = TALER_AML_NORMAL;
}
switch (decision)
{
case TALER_AML_NORMAL:
if (0 >= TALER_amount_cmp (&total,
&threshold))
{
/* total <= threshold, do nothing */
return true;
}
qs = db_plugin->trigger_aml_process (db_plugin->cls,
&au_active->h_payto,
&total);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
return false;
}
return false;
case TALER_AML_PENDING:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"AML already pending, doing nothing\n");
return false;
case TALER_AML_FROZEN:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Account frozen, doing nothing\n");
return false;
}
GNUNET_assert (0);
return false;
}
@ -772,8 +643,7 @@ do_aggregate (struct AggregationUnit *au)
TALER_amount_round_down (&au->final_amount,
&currency_round_unit)) ||
(TALER_amount_is_zero (&au->final_amount)) ||
(! kyc_satisfied (au)) ||
(! aml_satisfied (au)) )
(! kyc_satisfied (au)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Not ready for wire transfer (%d/%s)\n",

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2023 Taler Systems SA
Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@ -30,11 +30,9 @@
#include "taler_kyclogic_lib.h"
#include "taler_templating_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h"
#include "taler-exchange-httpd_batch-withdraw.h"
#include "taler-exchange-httpd_config.h"
#include "taler-exchange-httpd_contract.h"
#include "taler-exchange-httpd_csr.h"
#include "taler-exchange-httpd_deposit.h"
@ -80,11 +78,6 @@
*/
#define UNIX_BACKLOG 50
/**
* How often will we try to connect to the database before giving up?
*/
#define MAX_DB_RETRIES 5
/**
* Above what request latency do we start to log?
*/
@ -138,11 +131,6 @@ struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
*/
struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
* Key used to encrypt KYC attribute data in our database.
*/
struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
/**
* Our DB plugin. (global)
*/
@ -153,13 +141,6 @@ struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/
char *TEH_currency;
/**
* What is the largest amount we allow a peer to
* merge into a reserve before always triggering
* an AML check?
*/
struct TALER_Amount TEH_aml_threshold;
/**
* Our base URL.
*/
@ -341,227 +322,6 @@ 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[] = {
{
.op = "decisions",
.handler = &TEH_handler_aml_decisions_get
},
{
.op = "decision",
.handler = &TEH_handler_aml_decision_get
},
{
.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");
}
{
const char *sig_hdr;
struct TALER_AmlOfficerSignatureP officer_sig;
sig_hdr = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
TALER_AML_OFFICER_SIGNATURE_HEADER);
if ( (NULL == sig_hdr) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (sig_hdr,
strlen (sig_hdr),
&officer_sig,
sizeof (officer_sig))) ||
(GNUNET_OK !=
TALER_officer_aml_query_verify (&officer_pub,
&officer_sig)) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
sig_hdr);
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
}
{
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->test_aml_officer (TEH_plugin->cls,
&officer_pub);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
}
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.
*
@ -1130,8 +890,6 @@ handle_post_management (struct TEH_RequestContext *rc,
&exchange_pub,
root);
}
/* FIXME-STYLE: all of the following can likely be nicely combined
into an array-based dispatcher to deduplicate the logic... */
if (0 == strcmp (args[0],
"keys"))
{
@ -1209,30 +967,6 @@ handle_post_management (struct TEH_RequestContext *rc,
return TEH_handler_management_post_drain (rc->connection,
root);
}
if (0 == strcmp (args[0],
"aml-officers"))
{
if (NULL != args[1])
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/management/aml-officers/*");
}
return TEH_handler_management_aml_officers (rc->connection,
root);
}
if (0 == strcmp (args[0],
"partners"))
{
if (NULL != args[1])
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/management/partners/*");
}
return TEH_handler_management_partners (rc->connection,
root);
}
GNUNET_break_op (0);
return r404 (rc->connection,
"/management/*");
@ -1377,12 +1111,6 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handler_seed
},
/* Configuration */
{
.url = "config",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_config
},
/* Performance metrics */
{
.url = "metrics",
@ -1561,22 +1289,6 @@ handle_mhd_request (void *cls,
.nargs = 4,
.nargs_is_upper_bound = true
},
/* AML endpoints */
{
.url = "aml",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handle_get_aml,
.nargs = 4,
.nargs_is_upper_bound = true
},
{
.url = "aml",
.method = MHD_HTTP_METHOD_POST,
.handler.post = &handle_post_aml,
.nargs = 2
},
/* mark end of list */
{
.url = NULL
@ -1639,10 +1351,7 @@ handle_mhd_request (void *cls,
return MHD_NO;
}
if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
{
GNUNET_break_op (0);
return TALER_MHD_reply_request_too_large (connection);
}
}
}
}
@ -1879,23 +1588,6 @@ exchange_serve_process_config (void)
"CURRENCY");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_config_get_amount (TEH_cfg,
"taler",
"AML_THRESHOLD",
&TEH_aml_threshold))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Need amount in section `TALER' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR;
}
if (0 != strcmp (TEH_currency,
TEH_aml_threshold.currency))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Amount in section `TALER' under `AML_THRESHOLD' uses the wrong currency!\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
"exchange",
@ -1944,46 +1636,17 @@ exchange_serve_process_config (void)
GNUNET_free (master_public_key_str);
return GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Launching exchange with public key `%s'...\n",
master_public_key_str);
GNUNET_free (master_public_key_str);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Launching exchange with public key `%s'...\n",
GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
{
char *attr_enc_key_str;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
"exchange",
"ATTRIBUTE_ENCRYPTION_KEY",
&attr_enc_key_str))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
"ATTRIBUTE_ENCRYPTION_KEY");
return GNUNET_SYSERR;
}
GNUNET_CRYPTO_hash (attr_enc_key_str,
strlen (attr_enc_key_str),
&TEH_attribute_key.hash);
GNUNET_free (attr_enc_key_str);
}
for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
{
TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg);
if (NULL != TEH_plugin)
break;
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to connect to DB, will try again %u times\n",
MAX_DB_RETRIES - i);
sleep (1);
}
if (NULL == TEH_plugin)
if (NULL ==
(TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize DB subsystem. Giving up.\n");
"Failed to initialize DB subsystem\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;

View File

@ -82,11 +82,6 @@ extern bool TEH_suicide;
*/
extern struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
* Key used to encrypt KYC attribute data in our database.
*/
extern struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
/**
* Our DB plugin.
*/
@ -97,13 +92,6 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/
extern char *TEH_currency;
/**
* What is the largest amount we allow a peer to
* merge into a reserve before always triggering
* an AML check?
*/
extern struct TALER_Amount TEH_aml_threshold;
/**
* Our (externally visible) base URL.
*/
@ -225,7 +213,7 @@ struct TEH_RequestHandler
*
* @param rc context for the request
* @param json uploaded JSON data
* @param args array of arguments, needs to be of length @e nargs
* @param args array of arguments, needs to be of length @e args_expected
* @return MHD result code
*/
MHD_RESULT
@ -237,7 +225,7 @@ struct TEH_RequestHandler
* Function to call to handle DELETE requests.
*
* @param rc context for the request
* @param args array of arguments, needs to be of length @e nargs
* @param args array of arguments, needs to be of length @e args_expected
* @return MHD result code
*/
MHD_RESULT
@ -246,6 +234,17 @@ struct TEH_RequestHandler
} handler;
/**
* Number of arguments this handler expects in the @a args array.
*/
unsigned int nargs;
/**
* Is the number of arguments given in @e nargs only an upper bound,
* and calling with fewer arguments could be OK?
*/
bool nargs_is_upper_bound;
/**
* Mime type to use in reply (hint, can be NULL).
*/
@ -265,17 +264,6 @@ struct TEH_RequestHandler
* Default response code. 0 for none provided.
*/
unsigned int response_code;
/**
* Number of arguments this handler expects in the @a args array.
*/
unsigned int nargs;
/**
* Is the number of arguments given in @e nargs only an upper bound,
* and calling with fewer arguments could be OK?
*/
bool nargs_is_upper_bound;
};

View File

@ -1,395 +0,0 @@
/*
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_age-withdraw.c
* @brief Handle /reserves/$RESERVE_PUB/age-withdraw requests
* @author Özgür Kesim
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_age-withdraw.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keys.h"
/**
* Send a response to a "age-withdraw" request.
*
* @param connection the connection to send the response to
* @param ach value the client committed to
* @param noreveal_index which index will the client not have to reveal
* @return a MHD status code
*/
static MHD_RESULT
reply_age_withdraw_success (
struct MHD_Connection *connection,
const struct TALER_AgeWithdrawCommitmentHashP *ach,
uint32_t noreveal_index)
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
enum TALER_ErrorCode ec =
TALER_exchange_online_age_withdraw_confirmation_sign (
&TEH_keys_exchange_sign_,
ach,
noreveal_index,
&pub,
&sig);
if (TALER_EC_NONE != ec)
return TALER_MHD_reply_with_ec (connection,
ec,
NULL);
return TALER_MHD_REPLY_JSON_PACK (connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_uint64 ("noreveal_index",
noreveal_index),
GNUNET_JSON_pack_data_auto ("exchange_sig",
&sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub));
}
/**
* Context for #age_withdraw_transaction.
*/
struct AgeWithdrawContext
{
/**
* KYC status for the operation.
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
/**
* Hash of the wire source URL, needed when kyc is needed.
*/
struct TALER_PaytoHashP h_payto;
/**
* The data from the age-withdraw request
*/
struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;
/**
* Current time for the DB transaction.
*/
struct GNUNET_TIME_Timestamp now;
};
/**
* Function called to iterate over KYC-relevant
* transaction amounts for a particular time range.
* Called within a database transaction, so must
* not start a new one.
*
* @param cls closure, identifies the event type and
* account to iterate over events for
* @param limit maximum time-range for which events
* should be fetched (timestamp in the past)
* @param cb function to call on each event found,
* events must be returned in reverse chronological
* order
* @param cb_cls closure for @a cb
*/
static void
age_withdraw_amount_cb (void *cls,
struct GNUNET_TIME_Absolute limit,
TALER_EXCHANGEDB_KycAmountCallback cb,
void *cb_cls)
{
struct AgeWithdrawContext *awc = cls;
enum GNUNET_DB_QueryStatus qs;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Signaling amount %s for KYC check during age-withdrawal\n",
TALER_amount2s (&awc->commitment.amount_with_fee));
if (GNUNET_OK !=
cb (cb_cls,
&awc->commitment.amount_with_fee,
awc->now.abs_time))
return;
qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (TEH_plugin->cls,
&awc->h_payto,
limit,
cb,
cb_cls);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got %d additional transactions for this age-withdrawal and limit %llu\n",
qs,
(unsigned long long) limit.abs_value_us);
GNUNET_break (qs >= 0);
}
/**
* Function implementing age withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction
* logic MUST NOT queue a MHD response. IF it returns an hard error,
* the transaction logic MUST queue a MHD response and set @a mhd_ret.
* IF it returns the soft error code, the function MAY be called again
* to retry and MUST not queue a MHD response.
*
* Note that "awc->commitment.sig" is set before entering this function as we
* signed before entering the transaction.
*
* @param cls a `struct AgeWithdrawContext *`
* @param connection MHD request which triggered the transaction
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
age_withdraw_transaction (void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
struct AgeWithdrawContext *awc = cls;
enum GNUNET_DB_QueryStatus qs;
bool found = false;
bool balance_ok = false;
uint64_t ruuid;
awc->now = GNUNET_TIME_timestamp_get ();
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
&awc->commitment.reserve_pub,
&awc->h_payto);
if (qs < 0)
return qs;
/* If no results, reserve was created by merge,
in which case no KYC check is required as the
merge already did that. */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
char *kyc_required;
qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW,
&awc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&age_withdraw_amount_cb,
awc,
&kyc_required);
if (qs < 0)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
}
return qs;
}
if (NULL != kyc_required)
{
/* insert KYC requirement into DB! */
awc->kyc.ok = false;
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_required,
&awc->h_payto,
&awc->kyc.requirement_row);
}
}
awc->kyc.ok = true;
qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls,
&awc->commitment,
&found,
&balance_ok,
&ruuid);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"do_age_withdraw");
return qs;
}
else if (! found)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
else if (! balance_ok)
{
TEH_plugin->rollback (TEH_plugin->cls);
*mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
connection,
TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS,
&awc->commitment.amount_with_fee,
&awc->commitment.reserve_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW]++;
return qs;
}
/**
* Check if the @a rc is replayed and we already have an
* answer. If so, replay the existing answer and return the
* HTTP response.
*
* @param rc request context
* @param[in,out] awc parsed request data
* @param[out] mret HTTP status, set if we return true
* @return true if the request is idempotent with an existing request
* false if we did not find the request in the DB and did not set @a mret
*/
static bool
request_is_idempotent (struct TEH_RequestContext *rc,
struct AgeWithdrawContext *awc,
MHD_RESULT *mret)
{
enum GNUNET_DB_QueryStatus qs;
struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;
qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls,
&awc->commitment.reserve_pub,
&awc->commitment.h_commitment,
&commitment);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mret = TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_age_withdraw_info");
return true; /* well, kind-of */
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return false;
/* generate idempotent reply */
TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW]++;
*mret = reply_age_withdraw_success (rc->connection,
&commitment.h_commitment,
commitment.noreveal_index);
return true;
}
MHD_RESULT
TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
const struct TALER_ReservePublicKeyP *reserve_pub,
const json_t *root)
{
MHD_RESULT mhd_ret;
struct AgeWithdrawContext awc = {0};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&awc.commitment.reserve_sig),
GNUNET_JSON_spec_fixed_auto ("h_commitment",
&awc.commitment.h_commitment),
TALER_JSON_spec_amount ("amount",
TEH_currency,
&awc.commitment.amount_with_fee),
GNUNET_JSON_spec_uint16 ("max_age",
&awc.commitment.max_age),
GNUNET_JSON_spec_end ()
};
awc.commitment.reserve_pub = *reserve_pub;
/* Parse the JSON body */
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (rc->connection,
root,
spec);
if (GNUNET_OK != res)
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
do {
/* If request was made before successfully, return the previous answer */
if (request_is_idempotent (rc,
&awc,
&mhd_ret))
break;
/* Verify the signature of the request body with the reserve key */
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_wallet_age_withdraw_verify (&awc.commitment.h_commitment,
&awc.commitment.amount_with_fee,
awc.commitment.max_age,
&awc.commitment.reserve_pub,
&awc.commitment.reserve_sig))
{
GNUNET_break_op (0);
mhd_ret = TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
NULL);
break;
}
/* Run the transaction */
if (GNUNET_OK !=
TEH_DB_run_transaction (rc->connection,
"run age withdraw",
TEH_MT_REQUEST_AGE_WITHDRAW,
&mhd_ret,
&age_withdraw_transaction,
&awc))
break;
/* Clean up and send back final response */
GNUNET_JSON_parse_free (spec);
if (! awc.kyc.ok)
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&awc.h_payto,
&awc.kyc);
return reply_age_withdraw_success (rc->connection,
&awc.commitment.h_commitment,
awc.commitment.noreveal_index);
} while(0);
GNUNET_JSON_parse_free (spec);
return mhd_ret;
}
/* end of taler-exchange-httpd_age-withdraw.c */

View File

@ -1,47 +0,0 @@
/*
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_age-withdraw.h
* @brief Handle /reserve/$RESERVE_PUB/age-withdraw requests
* @author Özgür Kesim
*/
#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H
#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/reserves/$RESERVE_PUB/age-withdraw" request.
*
* Parses the batch of commitments to withdraw age restricted coins, and checks
* that the signature "reserve_sig" makes this a valid withdrawal request from
* the specified reserve. If the request is valid, the response contains a
* noreveal_index which the client has to use for the subsequent call to
* /age-withdraw/$ACH/reveal.
*
* @param rc request context
* @param root uploaded JSON data
* @param reserve_pub public key of the reserve
* @return MHD result code
*/
MHD_RESULT
TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
const struct TALER_ReservePublicKeyP *reserve_pub,
const json_t *root);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
/*
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_age-withdraw_reveal.h
* @brief Handle /age-withdraw/$ACH/reveal requests
* @author Özgür Kesim
*/
#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H
#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/age-withdraw/$ACH/reveal" request.
*
* The client got a noreveal_index in response to a previous request
* /reserve/$RESERVE_PUB/age-withdraw. It now has to reveal all n*(kappa-1)
* coin's private keys (except for the noreveal_index), from which all other
* coin-relevant data (blinding, age restriction, nonce) is derived from.
*
* The exchange computes those values, ensures that the maximum age is
* correctly applied, calculates the hash of the blinded envelopes, and -
* together with the non-disclosed blinded envelopes - compares the hash of
* those against the original commitment $ACH.
*
* If all those checks and the used denominations turn out to be correct, the
* exchange signs all blinded envelopes with their appropriate denomination
* keys.
*
* @param rc request context
* @param root uploaded JSON data
* @param ach commitment to the age restricted coints from the age-withdraw request
* @return MHD result code
*/
MHD_RESULT
TEH_handler_age_withdraw_reveal (
struct TEH_RequestContext *rc,
const struct TALER_AgeWithdrawCommitmentHashP *ach,
const json_t *root);
#endif

View File

@ -1,236 +0,0 @@
/*
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-get.c
* @brief Return summary information about AML decision
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd.h"
#include "taler_exchangedb_plugin.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_metrics.h"
/**
* Maximum number of records we return per request.
*/
#define MAX_RECORDS 1024
/**
* Callback with KYC attributes about a particular user.
*
* @param[in,out] cls closure with a `json_t *` array to update
* @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
*/
static void
kyc_attribute_cb (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes)
{
json_t *kyc_attributes = cls;
json_t *attributes;
attributes = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
enc_attributes,
enc_attributes_size);
GNUNET_break (NULL != attributes);
GNUNET_assert (
0 ==
json_array_append (
kyc_attributes,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("provider_section",
provider_section),
GNUNET_JSON_pack_timestamp ("collection_time",
collection_time),
GNUNET_JSON_pack_timestamp ("expiration_time",
expiration_time),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_steal ("attributes",
attributes))
)));
}
/**
* Return historic AML decision(s).
*
* @param[in,out] cls closure with a `json_t *` array to update
* @param new_threshold new monthly threshold that would trigger an AML check
* @param new_state AML decision status
* @param decision_time when was the decision made
* @param justification human-readable text justifying the decision
* @param decider_pub public key of the staff member
* @param decider_sig signature of the staff member
*/
static void
aml_history_cb (
void *cls,
const struct TALER_Amount *new_threshold,
enum TALER_AmlDecisionState new_state,
struct GNUNET_TIME_Timestamp decision_time,
const char *justification,
const struct TALER_AmlOfficerPublicKeyP *decider_pub,
const struct TALER_AmlOfficerSignatureP *decider_sig)
{
json_t *aml_history = cls;
GNUNET_assert (
0 ==
json_array_append (
aml_history,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("decider_pub",
decider_pub),
GNUNET_JSON_pack_string ("justification",
justification),
TALER_JSON_pack_amount ("new_threshold",
new_threshold),
GNUNET_JSON_pack_int64 ("new_state",
new_state),
GNUNET_JSON_pack_timestamp ("decision_time",
decision_time)
)));
}
MHD_RESULT
TEH_handler_aml_decision_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[])
{
struct TALER_PaytoHashP h_payto;
if ( (NULL == args[0]) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&h_payto,
sizeof (h_payto))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
if (NULL != args[1])
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
args[1]);
}
{
json_t *aml_history;
json_t *kyc_attributes;
enum GNUNET_DB_QueryStatus qs;
bool none = false;
aml_history = json_array ();
GNUNET_assert (NULL != aml_history);
qs = TEH_plugin->select_aml_history (TEH_plugin->cls,
&h_payto,
&aml_history_cb,
aml_history);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
json_decref (aml_history);
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
none = true;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
none = false;
break;
}
kyc_attributes = json_array ();
GNUNET_assert (NULL != kyc_attributes);
qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
&h_payto,
&kyc_attribute_cb,
kyc_attributes);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
json_decref (aml_history);
json_decref (kyc_attributes);
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
if (none)
{
json_decref (aml_history);
json_decref (kyc_attributes);
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("aml_history",
aml_history),
GNUNET_JSON_pack_array_steal ("kyc_attributes",
kyc_attributes));
}
}
/* end of taler-exchange-httpd_aml-decision_get.c */

View File

@ -1,374 +0,0 @@
/*
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.c
* @brief Handle request about an AML decision.
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd_responses.h"
/**
* Closure for #make_aml_decision()
*/
struct DecisionContext
{
/**
* Justification given for the decision.
*/
const char *justification;
/**
* When was the decision taken.
*/
struct GNUNET_TIME_Timestamp decision_time;
/**
* New threshold for revising the decision.
*/
struct TALER_Amount new_threshold;
/**
* Hash of payto://-URI of affected account.
*/
struct TALER_PaytoHashP h_payto;
/**
* New AML state.
*/
enum TALER_AmlDecisionState new_state;
/**
* Signature affirming the decision.
*/
struct TALER_AmlOfficerSignatureP officer_sig;
/**
* Public key of the AML officer.
*/
const struct TALER_AmlOfficerPublicKeyP *officer_pub;
/**
* KYC requirements imposed, NULL for none.
*/
json_t *kyc_requirements;
};
/**
* Function implementing AML decision database transaction.
*
* Runs the transaction logic; IF it returns a non-error code, the
* transaction logic MUST NOT queue a MHD response. IF it returns an hard
* error, the transaction logic MUST queue a MHD response and set @a mhd_ret.
* IF it returns the soft error code, the function MAY be called again to
* retry and MUST not queue a MHD response.
*
* @param cls closure with a `struct DecisionContext`
* @param connection MHD request which triggered the transaction
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
make_aml_decision (void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
struct DecisionContext *dc = cls;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp last_date;
bool invalid_officer;
uint64_t requirement_row = 0;
if ( (NULL != dc->kyc_requirements) &&
(0 != json_array_size (dc->kyc_requirements)) )
{
char *res = NULL;
size_t idx;
json_t *req;
bool satisfied;
json_array_foreach (dc->kyc_requirements, idx, req)
{
const char *r = json_string_value (req);
if (NULL == res)
{
res = GNUNET_strdup (r);
}
else
{
char *tmp;
GNUNET_asprintf (&tmp,
"%s %s",
res,
r);
GNUNET_free (res);
res = tmp;
}
}
{
json_t *kyc_details = NULL;
qs = TALER_KYCLOGIC_check_satisfied (
&res,
&dc->h_payto,
&kyc_details,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&satisfied);
json_decref (kyc_details);
}
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_satisfied_kyc_processes");
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
}
if (! satisfied)
{
qs = TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
res,
&dc->h_payto,
&requirement_row);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
}
}
GNUNET_free (res);
}
qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
&dc->h_payto,
&dc->new_threshold,
dc->new_state,
dc->decision_time,
dc->justification,
dc->kyc_requirements,
requirement_row,
dc->officer_pub,
&dc->officer_sig,
&invalid_officer,
&last_date);
if (qs <= 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_aml_decision");
return GNUNET_DB_STATUS_HARD_ERROR;
}
return qs;
}
if (invalid_officer)
{
GNUNET_break_op (0);
*mhd_ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (GNUNET_TIME_timestamp_cmp (last_date,
>=,
dc->decision_time))
{
GNUNET_break_op (0);
*mhd_ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
MHD_RESULT
TEH_handler_post_aml_decision (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const json_t *root)
{
struct MHD_Connection *connection = rc->connection;
struct DecisionContext dc = {
.officer_pub = officer_pub
};
uint32_t new_state32;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("officer_sig",
&dc.officer_sig),
GNUNET_JSON_spec_fixed_auto ("h_payto",
&dc.h_payto),
TALER_JSON_spec_amount ("new_threshold",
TEH_currency,
&dc.new_threshold),
GNUNET_JSON_spec_string ("justification",
&dc.justification),
GNUNET_JSON_spec_timestamp ("decision_time",
&dc.decision_time),
GNUNET_JSON_spec_uint32 ("new_state",
&new_state32),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("kyc_requirements",
&dc.kyc_requirements),
NULL),
GNUNET_JSON_spec_end ()
};
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
root,
spec);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
{
GNUNET_break_op (0);
return MHD_YES; /* failure */
}
}
dc.new_state = (enum TALER_AmlDecisionState) new_state32;
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_officer_aml_decision_verify (dc.justification,
dc.decision_time,
&dc.new_threshold,
&dc.h_payto,
dc.new_state,
dc.kyc_requirements,
dc.officer_pub,
&dc.officer_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
NULL);
}
if (NULL != dc.kyc_requirements)
{
size_t index;
json_t *elem;
if (! json_is_array (dc.kyc_requirements))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"kyc_requirements must be an array");
}
json_array_foreach (dc.kyc_requirements, index, elem)
{
const char *val;
if (! json_is_string (elem))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"kyc_requirements array members must be strings");
}
val = json_string_value (elem);
if (GNUNET_SYSERR ==
TALER_KYCLOGIC_check_satisfiable (val))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_AML_DECISION_UNKNOWN_CHECK,
val);
}
}
}
{
MHD_RESULT mhd_ret;
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"make-aml-decision",
TEH_MT_REQUEST_OTHER,
&mhd_ret,
&make_aml_decision,
&dc))
{
GNUNET_JSON_parse_free (spec);
return mhd_ret;
}
}
GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
/* end of taler-exchange-httpd_aml-decision.c */

View File

@ -1,79 +0,0 @@
/*
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 a POST "/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);
/**
* Handle a GET "/aml/$OFFICER_PUB/decisions/$STATE" request. Parses the request
* details, checks the signatures and if appropriately authorized returns
* the matching decisions.
*
* @param rc request context
* @param officer_pub public key of the AML officer who made the request
* @param args GET arguments (should be the state)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_aml_decisions_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[]);
/**
* Handle a GET "/aml/$OFFICER_PUB/decision/$H_PAYTO" request. Parses the request
* details, checks the signatures and if appropriately authorized returns
* the AML history and KYC attributes for the account.
*
* @param rc request context
* @param officer_pub public key of the AML officer who made the request
* @param args GET arguments (should be one)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_aml_decision_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[]);
#endif

View File

@ -1,211 +0,0 @@
/*
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-decisions-get.c
* @brief Return summary information about AML decisions
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd.h"
#include "taler_exchangedb_plugin.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_metrics.h"
/**
* Maximum number of records we return per request.
*/
#define MAX_RECORDS 1024
/**
* Return AML status.
*
* @param cls closure
* @param row_id current row in AML status table
* @param h_payto account for which the attribute data is stored
* @param threshold currently monthly threshold that would trigger an AML check
* @param status what is the current AML decision
*/
static void
record_cb (
void *cls,
uint64_t row_id,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_Amount *threshold,
enum TALER_AmlDecisionState status)
{
json_t *records = cls;
GNUNET_assert (
0 ==
json_array_append (
records,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_payto",
h_payto),
GNUNET_JSON_pack_int64 ("current_state",
status),
TALER_JSON_pack_amount ("threshold",
threshold),
GNUNET_JSON_pack_int64 ("rowid",
row_id)
)));
}
MHD_RESULT
TEH_handler_aml_decisions_get (
struct TEH_RequestContext *rc,
const struct TALER_AmlOfficerPublicKeyP *officer_pub,
const char *const args[])
{
enum TALER_AmlDecisionState decision;
int delta = -20;
unsigned long long start = INT64_MAX;
const char *state_str = args[0];
if (NULL == state_str)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
args[0]);
}
if (0 == strcmp (state_str,
"pending"))
decision = TALER_AML_PENDING;
else if (0 == strcmp (state_str,
"frozen"))
decision = TALER_AML_FROZEN;
else if (0 == strcmp (state_str,
"normal"))
decision = TALER_AML_NORMAL;
else
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
state_str);
}
if (NULL != args[1])
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
args[1]);
}
{
const char *p;
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"start");
if (NULL != p)
{
char dummy;
if (1 != sscanf (p,
"%llu%c",
&start,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"start");
}
}
p = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"delta");
if (NULL != p)
{
char dummy;
if (1 != sscanf (p,
"%d%c",
&delta,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"delta");
}
}
}
{
json_t *records;
enum GNUNET_DB_QueryStatus qs;
records = json_array ();
GNUNET_assert (NULL != records);
if (INT_MIN == delta)
delta = INT_MIN + 1;
qs = TEH_plugin->select_aml_process (TEH_plugin->cls,
decision,
start,
GNUNET_MIN (MAX_RECORDS,
delta > 0
? delta
: -delta),
delta > 0,
&record_cb,
records);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
json_decref (records);
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("records",
records));
}
}
/* end of taler-exchange-httpd_aml-decisions_get.c */

View File

@ -233,8 +233,9 @@ again:
MHD_HTTP_OK,
GNUNET_JSON_pack_timestamp ("exchange_timestamp",
bdc->exchange_timestamp),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub),
GNUNET_JSON_pack_data_auto (
"exchange_pub",
&pub),
GNUNET_JSON_pack_array_steal ("exchange_sigs",
arr));
}
@ -267,12 +268,13 @@ batch_deposit_transaction (void *cls,
* insert or update the record. */
if (dc->has_policy)
{
qs = TEH_plugin->persist_policy_details (
TEH_plugin->cls,
&dc->policy_details,
&dc->policy_details_serial_id,
&dc->policy_details.accumulated_total,
&dc->policy_details.fulfillment_state);
qs = TEH_plugin->persist_policy_details (TEH_plugin->cls,
&dc->policy_details,
&dc->policy_details_serial_id,
&dc->policy_details.
accumulated_total,
&dc->policy_details.
fulfillment_state);
if (qs < 0)
return qs;
}
@ -293,9 +295,9 @@ batch_deposit_transaction (void *cls,
deposit,
known_coin_id,
&dc->h_payto,
dc->has_policy
? &dc->policy_details_serial_id
: NULL,
(dc->has_policy)
? &dc->policy_details_serial_id
: NULL,
&dc->exchange_timestamp,
&balance_ok,
&in_conflict);

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2023 Taler Systems SA
Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@ -77,11 +77,6 @@ struct BatchWithdrawContext
*/
const struct TALER_ReservePublicKeyP *reserve_pub;
/**
* request context
*/
const struct TEH_RequestContext *rc;
/**
* KYC status of the reserve used for the operation.
*/
@ -113,11 +108,6 @@ struct BatchWithdrawContext
*/
unsigned int planchets_length;
/**
* AML decision, #TALER_AML_NORMAL if we may proceed.
*/
enum TALER_AmlDecisionState aml_decision;
};
@ -160,127 +150,6 @@ batch_withdraw_amount_cb (void *cls,
}
/**
* Function called on each @a amount that was found to
* be relevant for the AML check as it was merged into
* the reserve.
*
* @param cls `struct TALER_Amount *` to total up the amounts
* @param amount encountered transaction amount
* @param date when was the amount encountered
* @return #GNUNET_OK to continue to iterate,
* #GNUNET_NO to abort iteration
* #GNUNET_SYSERR on internal error (also abort itaration)
*/
static enum GNUNET_GenericReturnValue
aml_amount_cb (
void *cls,
const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute date)
{
struct TALER_Amount *total = cls;
GNUNET_assert (0 <=
TALER_amount_add (total,
total,
amount));
return GNUNET_OK;
}
/**
* Generates our final (successful) response.
*
* @param rc request context
* @param wc operation context
* @return MHD queue status
*/
static MHD_RESULT
generate_reply_success (const struct TEH_RequestContext *rc,
const struct BatchWithdrawContext *wc)
{
json_t *sigs;
if (! wc->kyc.ok)
{
/* KYC required */
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&wc->h_payto,
&wc->kyc);
}
if (TALER_AML_NORMAL != wc->aml_decision)
return TEH_RESPONSE_reply_aml_blocked (rc->connection,
wc->aml_decision);
sigs = json_array ();
GNUNET_assert (NULL != sigs);
for (unsigned int i = 0; i<wc->planchets_length; i++)
{
struct PlanchetContext *pc = &wc->planchets[i];
GNUNET_assert (
0 ==
json_array_append_new (
sigs,
GNUNET_JSON_PACK (
TALER_JSON_pack_blinded_denom_sig (
"ev_sig",
&pc->collectable.sig))));
}
TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("ev_sigs",
sigs));
}
/**
* Check if the @a wc is replayed and we already have an
* answer. If so, replay the existing answer and return the
* HTTP response.
*
* @param wc parsed request data
* @param[out] mret HTTP status, set if we return true
* @return true if the request is idempotent with an existing request
* false if we did not find the request in the DB and did not set @a mret
*/
static bool
check_request_idempotent (const struct BatchWithdrawContext *wc,
MHD_RESULT *mret)
{
const struct TEH_RequestContext *rc = wc->rc;
for (unsigned int i = 0; i<wc->planchets_length; i++)
{
struct PlanchetContext *pc = &wc->planchets[i];
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
&pc->h_coin_envelope,
&pc->collectable);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mret = TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_withdraw_info");
return true; /* well, kind-of */
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return false;
}
/* generate idempotent reply */
TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
*mret = generate_reply_success (rc,
wc);
return true;
}
/**
* Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction
@ -308,116 +177,14 @@ batch_withdraw_transaction (void *cls,
enum GNUNET_DB_QueryStatus qs;
bool balance_ok = false;
bool found = false;
char *kyc_required;
struct TALER_PaytoHashP reserve_h_payto;
const char *kyc_required;
wc->now = GNUNET_TIME_timestamp_get ();
/* Do AML check: compute total merged amount and check
against applicable AML threshold */
{
char *reserve_payto;
reserve_payto = TALER_reserve_make_payto (TEH_base_url,
wc->reserve_pub);
TALER_payto_hash (reserve_payto,
&reserve_h_payto);
GNUNET_free (reserve_payto);
}
{
struct TALER_Amount merge_amount;
struct TALER_Amount threshold;
struct GNUNET_TIME_Absolute now_minus_one_month;
now_minus_one_month
= GNUNET_TIME_absolute_subtract (wc->now.abs_time,
GNUNET_TIME_UNIT_MONTHS);
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TEH_currency,
&merge_amount));
qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
&reserve_h_payto,
now_minus_one_month,
&aml_amount_cb,
&merge_amount);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_merge_amounts_for_kyc_check");
return qs;
}
qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
&reserve_h_payto,
&wc->aml_decision,
&wc->kyc,
&threshold);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_aml_threshold");
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
threshold = TEH_aml_threshold; /* use default */
wc->aml_decision = TALER_AML_NORMAL;
}
switch (wc->aml_decision)
{
case TALER_AML_NORMAL:
if (0 >= TALER_amount_cmp (&merge_amount,
&threshold))
{
/* merge_amount <= threshold, continue withdraw below */
break;
}
wc->aml_decision = TALER_AML_PENDING;
qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
&reserve_h_payto,
&merge_amount);
if (qs <= 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"trigger_aml_process");
return qs;
}
return qs;
case TALER_AML_PENDING:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"AML already pending, doing nothing\n");
return qs;
case TALER_AML_FROZEN:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Account frozen, doing nothing\n");
return qs;
}
}
/* Check if the money came from a wire transfer */
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
wc->reserve_pub,
&wc->h_payto);
if (qs < 0)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"reserves_get_origin");
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
@ -426,44 +193,22 @@ batch_withdraw_transaction (void *cls,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
qs = TALER_KYCLOGIC_kyc_test_required (
kyc_required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
&wc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&batch_withdraw_amount_cb,
wc,
&kyc_required);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
return qs;
}
wc);
if (NULL != kyc_required)
{
/* insert KYC requirement into DB! */
wc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_required,
&wc->h_payto,
&wc->kyc.requirement_row);
GNUNET_free (kyc_required);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
}
return qs;
}
wc->kyc.ok = true;
qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls,
@ -476,13 +221,10 @@ batch_withdraw_transaction (void *cls,
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"update_reserve_batch_withdraw");
}
return qs;
}
if (! found)
@ -546,18 +288,12 @@ batch_withdraw_transaction (void *cls,
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
(conflict) )
{
if (! check_request_idempotent (wc,
mhd_ret))
{
/* We do not support *some* of the coins of the request being
idempotent while others being fresh. */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Idempotent coin in batch, not allowed. Aborting.\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
NULL);
}
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Idempotent coin in batch, not allowed. Aborting.\n");
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (nonce_reuse)
@ -575,6 +311,96 @@ batch_withdraw_transaction (void *cls,
}
/**
* Generates our final (successful) response.
*
* @param rc request context
* @param wc operation context
* @return MHD queue status
*/
static MHD_RESULT
generate_reply_success (const struct TEH_RequestContext *rc,
const struct BatchWithdrawContext *wc)
{
json_t *sigs;
if (! wc->kyc.ok)
{
/* KYC required */
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&wc->h_payto,
&wc->kyc);
}
sigs = json_array ();
GNUNET_assert (NULL != sigs);
for (unsigned int i = 0; i<wc->planchets_length; i++)
{
struct PlanchetContext *pc = &wc->planchets[i];
GNUNET_assert (
0 ==
json_array_append_new (
sigs,
GNUNET_JSON_PACK (
TALER_JSON_pack_blinded_denom_sig (
"ev_sig",
&pc->collectable.sig))));
}
TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("ev_sigs",
sigs));
}
/**
* Check if the @a rc is replayed and we already have an
* answer. If so, replay the existing answer and return the
* HTTP response.
*
* @param rc request context
* @param wc parsed request data
* @param[out] mret HTTP status, set if we return true
* @return true if the request is idempotent with an existing request
* false if we did not find the request in the DB and did not set @a mret
*/
static bool
check_request_idempotent (const struct TEH_RequestContext *rc,
const struct BatchWithdrawContext *wc,
MHD_RESULT *mret)
{
for (unsigned int i = 0; i<wc->planchets_length; i++)
{
struct PlanchetContext *pc = &wc->planchets[i];
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
&pc->h_coin_envelope,
&pc->collectable);
if (0 > qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mret = TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_withdraw_info");
return true; /* well, kind-of */
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return false;
}
/* generate idempotent reply */
TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
*mret = generate_reply_success (rc,
wc);
return true;
}
/**
* The request was parsed successfully. Prepare
* our side for the main DB transaction.
@ -702,7 +528,8 @@ parse_planchets (const struct TEH_RequestContext *rc,
ksh = TEH_keys_get_state ();
if (NULL == ksh)
{
if (! check_request_idempotent (wc,
if (! check_request_idempotent (rc,
wc,
&mret))
{
return TALER_MHD_reply_with_error (rc->connection,
@ -723,7 +550,8 @@ parse_planchets (const struct TEH_RequestContext *rc,
NULL);
if (NULL == dk)
{
if (! check_request_idempotent (wc,
if (! check_request_idempotent (rc,
wc,
&mret))
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
@ -735,7 +563,8 @@ parse_planchets (const struct TEH_RequestContext *rc,
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
{
/* This denomination is past the expiration time for withdraws */
if (! check_request_idempotent (wc,
if (! check_request_idempotent (rc,
wc,
&mret))
{
return TEH_RESPONSE_reply_expired_denom_pub_hash (
@ -759,7 +588,8 @@ parse_planchets (const struct TEH_RequestContext *rc,
if (dk->recoup_possible)
{
/* This denomination has been revoked */
if (! check_request_idempotent (wc,
if (! check_request_idempotent (rc,
wc,
&mret))
{
return TEH_RESPONSE_reply_expired_denom_pub_hash (
@ -839,10 +669,7 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
const struct TALER_ReservePublicKeyP *reserve_pub,
const json_t *root)
{
struct BatchWithdrawContext wc = {
.reserve_pub = reserve_pub,
.rc = rc
};
struct BatchWithdrawContext wc;
json_t *planchets;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("planchets",
@ -850,9 +677,13 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
GNUNET_JSON_spec_end ()
};
memset (&wc,
0,
sizeof (wc));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TEH_currency,
&wc.batch_total));
wc.reserve_pub = reserve_pub;
{
enum GNUNET_GenericReturnValue res;

View File

@ -1,55 +0,0 @@
/*
This file is part of TALER
Copyright (C) 2015-2021 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_config.c
* @brief Handle /config requests
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_json_lib.h>
#include "taler_dbevents.h"
#include "taler-exchange-httpd_config.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include <jansson.h>
MHD_RESULT
TEH_handler_config (struct TEH_RequestContext *rc,
const char *const args[])
{
static struct MHD_Response *resp;
if (NULL == resp)
{
resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_steal ("supported_kyc_requirements",
TALER_KYCLOGIC_get_satisfiable ()),
GNUNET_JSON_pack_string ("currency",
TEH_currency),
GNUNET_JSON_pack_string ("name",
"taler-exchange"),
GNUNET_JSON_pack_string ("version",
EXCHANGE_PROTOCOL_VERSION));
}
return MHD_queue_response (rc->connection,
MHD_HTTP_OK,
resp);
}
/* end of taler-exchange-httpd_config.c */

View File

@ -1,58 +0,0 @@
/*
This file is part of TALER
(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 EXCHANGEABILITY 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/>
*/
/**
* @file taler-exchange-httpd_config.h
* @brief headers for /config handler
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_CONFIG_H
#define TALER_EXCHANGE_HTTPD_CONFIG_H
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Taler protocol version in the format CURRENT:REVISION:AGE
* as used by GNU libtool. See
* https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
*
* Please be very careful when updating and follow
* https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
* precisely. Note that this version has NOTHING to do with the
* release version, and the format is NOT the same that semantic
* versioning uses either.
*
* When changing this version, you likely want to also update
* #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
* exchange_api_handle.c!
*
* Returned via both /config and /keys endpoints.
*/
#define EXCHANGE_PROTOCOL_VERSION "14:0:2"
/**
* Manages a /config call.
*
* @param rc context of the handler
* @param[in,out] args remaining arguments (ignored)
* @return MHD result code
*/
MHD_RESULT
TEH_handler_config (struct TEH_RequestContext *rc,
const char *const args[]);
#endif

View File

@ -234,10 +234,12 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
.cipher = TALER_DENOMINATION_CS
};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("nonce",
&nonce),
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&denom_pub_hash),
GNUNET_JSON_spec_fixed ("nonce",
&nonce,
sizeof (struct TALER_CsNonce)),
GNUNET_JSON_spec_fixed ("denom_pub_hash",
&denom_pub_hash,
sizeof (struct TALER_DenominationHashP)),
GNUNET_JSON_spec_end ()
};
struct TEH_DenominationKey *dk;
@ -331,11 +333,17 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
}
}
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
TALER_JSON_pack_exchange_withdraw_values ("ewv",
&ewv));
{
json_t *csr_obj;
csr_obj = GNUNET_JSON_PACK (
TALER_JSON_pack_exchange_withdraw_values ("ewv",
&ewv));
GNUNET_assert (NULL != csr_obj);
return TALER_MHD_reply_json_steal (rc->connection,
csr_obj,
MHD_HTTP_OK);
}
}

View File

@ -90,11 +90,6 @@ struct DepositWtidContext
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
/**
* AML status information for the receiving account.
*/
enum TALER_AmlDecisionState aml_decision;
/**
* Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending
* (and the above were not set).
@ -133,7 +128,6 @@ reply_deposit_details (
&pub,
&sig)))
{
GNUNET_break (0);
return TALER_MHD_reply_with_ec (connection,
ec,
NULL);
@ -190,8 +184,7 @@ deposits_get_transaction (void *cls,
&ctx->execution_time,
&ctx->coin_contribution,
&fee,
&ctx->kyc,
&ctx->aml_decision);
&ctx->kyc);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@ -264,8 +257,6 @@ handle_track_transaction_request (
NULL)
: GNUNET_JSON_pack_uint64 ("requirement_row",
ctx->kyc.requirement_row)),
GNUNET_JSON_pack_uint64 ("aml_decision",
(uint32_t) ctx->aml_decision),
GNUNET_JSON_pack_bool ("kyc_ok",
ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time",

View File

@ -150,11 +150,12 @@ extension_update_event_cb (void *cls,
{
TEH_age_restriction_enabled = true;
TEH_age_restriction_config = *conf;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"[age restriction] DB event has changed the config to %s with mask: %s\n",
TEH_age_restriction_enabled ? "enabled": "DISABLED",
TALER_age_mask_to_string (&conf->mask));
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"[age restriction] DB event has changed the config to %s with mask: %s\n",
TEH_age_restriction_enabled ? "enabled": "DISABLED",
TALER_age_mask_to_string (&conf->mask));
}
// Finally, call TEH_keys_update_states in order to refresh the cached

View File

@ -25,7 +25,6 @@
#include "taler_kyclogic_lib.h"
#include "taler_dbevents.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_config.h"
#include "taler-exchange-httpd_keys.h"
#include "taler-exchange-httpd_responses.h"
#include "taler_exchangedb_plugin.h"
@ -45,6 +44,24 @@
#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES
/**
* Taler protocol version in the format CURRENT:REVISION:AGE
* as used by GNU libtool. See
* https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
*
* Please be very careful when updating and follow
* https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
* precisely. Note that this version has NOTHING to do with the
* release version, and the format is NOT the same that semantic
* versioning uses either.
*
* When changing this version, you likely want to also update
* #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
* exchange_api_handle.c!
*/
#define EXCHANGE_PROTOCOL_VERSION "14:0:2"
/**
* Information about a denomination on offer by the denomination helper.
*/
@ -738,7 +755,7 @@ free_denom_cb (void *cls,
* @param value the `struct HelperSignkey` to release
* @return #GNUNET_OK (continue to iterate)
*/
static enum GNUNET_GenericReturnValue
static int
free_esign_cb (void *cls,
const struct GNUNET_PeerIdentity *pid,
void *value)
@ -1905,7 +1922,6 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *extensions = json_object ();
bool has_extensions = false;
GNUNET_assert (NULL != extensions);
/* Fill in the configurations of the enabled extensions */
for (const struct TALER_Extensions *iter = TALER_extensions_get_head ();
NULL != iter && NULL != iter->extension;
@ -1939,7 +1955,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *sig;
int r;
r = json_object_set_new (
r = json_object_set (
keys,
"extensions",
extensions);
@ -2259,9 +2275,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
if (age_restricted)
{
int r = json_object_set_new (group->json,
"age_mask",
json_integer (meta.age_mask.bits));
int r = json_object_set (group->json,
"age_mask",
json_integer (meta.age_mask.bits));
GNUNET_assert (0 == r);
}
@ -2269,9 +2285,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
list = json_array ();
GNUNET_assert (NULL != list);
GNUNET_assert (0 ==
json_object_set_new (group->json,
denoms_key,
list));
json_object_set (group->json, denoms_key, list));
GNUNET_assert (
GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
@ -2355,7 +2370,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
/* Add the XOR over all hashes of denominations in this group to the group */
GNUNET_assert (0 ==
json_object_set_new (
json_object_set (
group->json,
"hash",
GNUNET_JSON_PACK (
@ -2377,10 +2392,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
GNUNET_CONTAINER_multihashmap_iterator_destroy (iter);
GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
}
GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
}
GNUNET_CONTAINER_heap_destroy (heap);
@ -2404,7 +2418,6 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
"Failed to generate key response data for %s\n",
GNUNET_TIME_timestamp2s (last_cpd));
json_decref (denoms);
json_decref (grouped_denominations);
json_decref (sctx.signkeys);
json_decref (recoup);
return GNUNET_SYSERR;
@ -2417,7 +2430,6 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
"No denomination keys available. Refusing to generate /keys response.\n");
GNUNET_CRYPTO_hash_context_abort (hash_context);
}
json_decref (grouped_denominations);
json_decref (sctx.signkeys);
json_decref (recoup);
json_decref (denoms);
@ -3617,7 +3629,6 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) &&
(GNUNET_is_zero (&denom_cs_sm_pub)) )
{
/* Either IPC failed, or neither helper had any denominations configured. */
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_GATEWAY,
TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
@ -3630,6 +3641,7 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
NULL);
}
// then a secmod helper is not yet running and we should return an MHD_HTTP_BAD_GATEWAY!
GNUNET_assert (NULL != fbc.denoms);
GNUNET_assert (NULL != fbc.signkeys);
GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys,

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2021-2023 Taler Systems SA
Copyright (C) 2021-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@ -112,11 +112,6 @@ struct KycPoller
*/
const char *section_name;
/**
* Set to AML status of the account.
*/
enum TALER_AmlDecisionState aml_status;
/**
* Set to error encountered with KYC logic, if any.
*/
@ -238,7 +233,7 @@ initiate_cb (
kyp->ih = NULL;
kyp->ih_done = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC initiation completed with ec=%d (%s)\n",
"KYC initiation completed with status %d (%s)\n",
ec,
(TALER_EC_NONE == ec)
? redirect_url
@ -302,13 +297,11 @@ kyc_check (void *cls,
enum GNUNET_GenericReturnValue ret;
struct TALER_PaytoHashP h_payto;
char *requirements;
bool satisfied;
qs = TEH_plugin->lookup_kyc_requirement_by_row (
TEH_plugin->cls,
kyp->requirement_row,
&requirements,
&kyp->aml_status,
&h_payto);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
@ -337,26 +330,12 @@ kyc_check (void *cls,
GNUNET_free (requirements);
return GNUNET_DB_STATUS_HARD_ERROR;
}
qs = TALER_KYCLOGIC_check_satisfied (
&requirements,
&h_payto,
&kyp->kyc_details,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&satisfied);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
GNUNET_free (requirements);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (satisfied)
if (TALER_KYCLOGIC_check_satisfied (
requirements,
&h_payto,
&kyp->kyc_details,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirements `%s' already satisfied\n",
@ -395,17 +374,6 @@ kyc_check (void *cls,
NULL,
NULL,
&kyp->process_row);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_process");
return GNUNET_DB_STATUS_HARD_ERROR;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Initiating KYC check with logic %s\n",
kyp->ih_logic->name);
@ -586,17 +554,6 @@ TEH_handler_kyc_check (
if ( (NULL == kyp->ih) &&
(! kyp->kyc_required) )
{
if (TALER_AML_NORMAL != kyp->aml_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC is OK, but AML active: %d\n",
(int) kyp->aml_status);
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("aml_status",
kyp->aml_status));
}
/* KYC not required */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC not required %llu\n",
@ -645,8 +602,6 @@ TEH_handler_kyc_check (
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_uint64 ("aml_status",
kyp->aml_status),
GNUNET_JSON_pack_string ("kyc_url",
kyp->kyc_url));
}
@ -684,8 +639,6 @@ TEH_handler_kyc_check (
&sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub),
GNUNET_JSON_pack_uint64 ("aml_status",
kyp->aml_status),
GNUNET_JSON_pack_object_incref ("kyc_details",
kyp->kyc_details),
GNUNET_JSON_pack_timestamp ("now",

View File

@ -24,7 +24,6 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
@ -170,7 +169,6 @@ TEH_kyc_proof_cleanup (void)
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param expiration until when is the KYC check valid
* @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
*/
@ -181,7 +179,6 @@ proof_cb (
const char *provider_user_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response)
{
@ -196,41 +193,7 @@ proof_cb (
if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
TALER_CRYPTO_attributes_to_kyc_prox (attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
&kpc->h_payto,
&kyc_prox,
kpc->provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != response)
MHD_destroy_response (response);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
GNUNET_async_scope_restore (&old_scope);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kpc->process_row,
kpc->provider_section,

View File

@ -54,7 +54,7 @@ struct KycRequestContext
/**
* Name of the required check.
*/
char *required;
const char *required;
};
@ -109,34 +109,22 @@ wallet_kyc_check (void *cls,
struct KycRequestContext *krc = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TALER_KYCLOGIC_kyc_test_required (
krc->required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
&krc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&balance_iterator,
krc,
&krc->required);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
return qs;
}
krc);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check required at %s is `%s'\n",
TALER_amount2s (&krc->balance),
krc->required);
if (NULL == krc->required)
{
krc->kyc.ok = true;
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check required at %s is `%s'\n",
TALER_amount2s (&krc->balance),
krc->required);
krc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (TEH_plugin->cls,
krc->required,
@ -237,7 +225,6 @@ TEH_handler_kyc_wallet (
NULL,
0);
}
GNUNET_free (krc.required);
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&krc.h_payto,
&krc.kyc);

View File

@ -24,7 +24,6 @@
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
@ -141,7 +140,8 @@ TEH_kyc_webhook_cleanup (void)
/**
* Function called with the result of a KYC webhook operation.
* Function called with the result of a webhook
* operation.
*
* Note that the "decref" for the @a response
* will be done by the plugin.
@ -154,7 +154,6 @@ TEH_kyc_webhook_cleanup (void)
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param status KYC status
* @param expiration until when is the KYC check valid
* @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
*/
@ -168,7 +167,6 @@ webhook_finished_cb (
const char *provider_legitimization_id,
enum TALER_KYCLOGIC_KycStatus status,
struct GNUNET_TIME_Absolute expiration,
const json_t *attributes,
unsigned int http_status,
struct MHD_Response *response)
{
@ -181,39 +179,7 @@ webhook_finished_cb (
/* _successfully_ resumed case */
{
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
TALER_CRYPTO_attributes_to_kyc_prox (attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
account_id,
&kyc_prox,
provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (qs < 0)
{
GNUNET_break (0);
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh_resume (kwh);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
process_row,
provider_section,
@ -296,12 +262,11 @@ handler_kyc_webhook_generic (
rc->rh_ctx = kwh;
rc->rh_cleaner = &clean_kwh;
if ( (NULL == args[0]) ||
(GNUNET_OK !=
TALER_KYCLOGIC_lookup_logic (args[0],
&kwh->plugin,
&kwh->pd,
&kwh->provider_section)) )
if (GNUNET_OK !=
TALER_KYCLOGIC_lookup_logic (args[0],
&kwh->plugin,
&kwh->pd,
&kwh->provider_section))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"KYC logic `%s' unknown (check KYC provider configuration)\n",
@ -309,7 +274,7 @@ handler_kyc_webhook_generic (
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
args[0]);
"$NAME");
}
kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
kwh->pd,

View File

@ -174,32 +174,6 @@ TEH_handler_management_post_drain (
const json_t *root);
/**
* Handle a POST "/management/aml-officers" request.
*
* @param connection the MHD connection to handle
* @param root uploaded JSON data
* @return MHD result code
*/
MHD_RESULT
TEH_handler_management_aml_officers (
struct MHD_Connection *connection,
const json_t *root);
/**
* Handle a POST "/management/partners" request.
*
* @param connection the MHD connection to handle
* @param root uploaded JSON data
* @return MHD result code
*/
MHD_RESULT
TEH_handler_management_partners (
struct MHD_Connection *connection,
const json_t *root);
/**
* Initialize extension configuration handling.
*

View File

@ -1,142 +0,0 @@
/*
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_management_aml-officers.c
* @brief Handle request to update AML officer status
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
/**
* How often do we try the DB operation at most?
*/
#define MAX_RETRIES 10
MHD_RESULT
TEH_handler_management_aml_officers (
struct MHD_Connection *connection,
const json_t *root)
{
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 GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("officer_pub",
&officer_pub),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&master_sig),
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_timestamp ("change_date",
&change_date),
GNUNET_JSON_spec_end ()
};
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
root,
spec);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
return MHD_YES; /* failure */
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_exchange_offline_aml_officer_status_verify (
&officer_pub,
officer_name,
change_date,
is_active,
read_only,
&TEH_master_public_key,
&master_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_MANAGEMENT_UPDATE_AML_OFFICER_SIGNATURE_INVALID,
NULL);
}
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp last_date;
unsigned int retries_left = MAX_RETRIES;
do {
qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls,
&officer_pub,
&master_sig,
officer_name,
is_active,
read_only,
change_date,
&last_date);
if (0 == --retries_left)
break;
} while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (qs < 0)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_aml_officer");
}
if (GNUNET_TIME_timestamp_cmp (last_date,
>,
change_date))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_MANAGEMENT_AML_OFFICERS_MORE_RECENT_PRESENT,
NULL);
}
}
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
/* end of taler-exchange-httpd_management_aml-officers.c */

View File

@ -1,132 +0,0 @@
/*
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_management_partners.c
* @brief Handle request to add exchange partner
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
MHD_RESULT
TEH_handler_management_partners (
struct MHD_Connection *connection,
const json_t *root)
{
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 GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("partner_pub",
&partner_pub),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&master_sig),
GNUNET_JSON_spec_string ("partner_base_url",
&partner_base_url),
TALER_JSON_spec_amount ("wad_fee",
TEH_currency,
&wad_fee),
GNUNET_JSON_spec_timestamp ("start_date",
&start_date),
GNUNET_JSON_spec_timestamp ("end_date",
&end_date),
GNUNET_JSON_spec_relative_time ("wad_frequency",
&wad_frequency),
GNUNET_JSON_spec_end ()
};
{
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection,
root,
spec);
if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */
if (GNUNET_NO == res)
return MHD_YES; /* failure */
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_exchange_offline_partner_details_verify (
&partner_pub,
start_date,
end_date,
wad_frequency,
&wad_fee,
partner_base_url,
&TEH_master_public_key,
&master_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_SIGNATURE_INVALID,
NULL);
}
{
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->insert_partner (TEH_plugin->cls,
&partner_pub,
start_date,
end_date,
wad_frequency,
&wad_fee,
partner_base_url,
&master_sig);
if (qs < 0)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"add_partner");
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* FIXME: check for idempotency! */
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_CONFLICT,
TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_DATA_CONFLICT,
NULL);
}
}
return TALER_MHD_reply_static (
connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
/* end of taler-exchange-httpd_management_partners.c */

View File

@ -34,20 +34,18 @@ enum TEH_MetricTypeRequest
TEH_MT_REQUEST_OTHER = 0,
TEH_MT_REQUEST_DEPOSIT = 1,
TEH_MT_REQUEST_WITHDRAW = 2,
TEH_MT_REQUEST_AGE_WITHDRAW = 3,
TEH_MT_REQUEST_MELT = 4,
TEH_MT_REQUEST_PURSE_CREATE = 5,
TEH_MT_REQUEST_PURSE_MERGE = 6,
TEH_MT_REQUEST_RESERVE_PURSE = 7,
TEH_MT_REQUEST_PURSE_DEPOSIT = 8,
TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9,
TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10,
TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11,
TEH_MT_REQUEST_IDEMPOTENT_MELT = 12,
TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13,
TEH_MT_REQUEST_BATCH_DEPOSIT = 14,
TEH_MT_REQUEST_POLICY_FULFILLMENT = 15,
TEH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */
TEH_MT_REQUEST_MELT = 3,
TEH_MT_REQUEST_PURSE_CREATE = 4,
TEH_MT_REQUEST_PURSE_MERGE = 5,
TEH_MT_REQUEST_RESERVE_PURSE = 6,
TEH_MT_REQUEST_PURSE_DEPOSIT = 7,
TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 8,
TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 9,
TEH_MT_REQUEST_IDEMPOTENT_MELT = 10,
TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11,
TEH_MT_REQUEST_BATCH_DEPOSIT = 12,
TEH_MT_REQUEST_POLICY_FULFILLMENT = 13,
TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */
};
/**
@ -57,12 +55,10 @@ enum TEH_MetricTypeSuccess
{
TEH_MT_SUCCESS_DEPOSIT = 0,
TEH_MT_SUCCESS_WITHDRAW = 1,
TEH_MT_SUCCESS_AGE_WITHDRAW = 2,
TEH_MT_SUCCESS_BATCH_WITHDRAW = 3,
TEH_MT_SUCCESS_MELT = 4,
TEH_MT_SUCCESS_REFRESH_REVEAL = 5,
TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6,
TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */
TEH_MT_SUCCESS_BATCH_WITHDRAW = 2,
TEH_MT_SUCCESS_MELT = 3,
TEH_MT_SUCCESS_REFRESH_REVEAL = 4,
TEH_MT_SUCCESS_COUNT = 5 /* MUST BE LAST! */
};
/**

View File

@ -435,8 +435,6 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
&exchange_sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
GNUNET_JSON_pack_timestamp ("purse_expiration",
gc->purse_expiration),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("merge_timestamp",
gc->merge_timestamp)),

View File

@ -280,47 +280,23 @@ merge_transaction (void *cls,
bool in_conflict = true;
bool no_balance = true;
bool no_partner = true;
char *required;
const char *required;
qs = TALER_KYCLOGIC_kyc_test_required (
required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
&pcc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_iterator,
pcc,
&required);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
return qs;
}
pcc);
if (NULL != required)
{
pcc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
required,
&pcc->h_payto,
&pcc->kyc.requirement_row);
GNUNET_free (required);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
}
return qs;
}
pcc->kyc.ok = true;
qs = TEH_plugin->do_purse_merge (
@ -338,7 +314,8 @@ merge_transaction (void *cls,
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
TALER_LOG_WARNING (
"Failed to store merge purse information in database\n");
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,

View File

@ -773,17 +773,12 @@ clean_age:
NULL);
goto cleanup;
}
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
rrcs[i].coin_sig = bss[i];
rrcs[i].blinded_planchet = rcds[i].blinded_planchet;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Signatures ready, starting DB interaction\n");
for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
{
bool changed;
@ -800,7 +795,12 @@ clean_age:
NULL);
goto cleanup;
}
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
rrc->blinded_planchet = rcds[i].blinded_planchet;
}
qs = TEH_plugin->insert_refresh_reveal (
TEH_plugin->cls,
melt_serial_id,

View File

@ -76,14 +76,14 @@ struct ReserveAttestContext
struct TALER_ReserveSignatureP reserve_sig;
/**
* Attributes we are affirming. JSON object.
* Attributes we are affirming.
*/
json_t *json_attest;
/**
* Database error codes encountered.
* Error code encountered in interaction with KYC provider.
*/
enum GNUNET_DB_QueryStatus qs;
enum TALER_ErrorCode ec;
/**
* Set to true if we did not find the reserve.
@ -140,12 +140,8 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
&exchange_sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
GNUNET_JSON_pack_timestamp ("exchange_timestamp",
now),
GNUNET_JSON_pack_timestamp ("expiration_time",
rhc->etime),
GNUNET_JSON_pack_object_steal ("attributes",
rhc->json_attest));
GNUNET_JSON_pack_array_steal ("attest",
rhc->json_attest));
}
@ -156,71 +152,68 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
* set based on the details requested by the client.
*
* @param cls our `struct ReserveAttestContext *`
* @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @param provider_section KYC provider configuration section
* @param provider_user_id UID at a provider (can be NULL)
* @param legi_id legitimization process ID (can be NULL)
*/
static void
kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes)
const char *provider_user_id,
const char *legi_id)
{
struct ReserveAttestContext *rsc = cls;
struct GNUNET_TIME_Timestamp etime;
json_t *attrs;
json_t *val;
const char *name;
bool match = false;
if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
provider_user_id,
legi_id,
&etime,
&attrs);
if (TALER_EC_NONE != rsc->ec)
return;
attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
enc_attributes,
enc_attributes_size);
json_object_foreach (attrs, name, val)
if (GNUNET_TIME_absolute_is_past (etime.abs_time))
{
bool requested = false;
size_t idx;
json_t *str;
json_decref (attrs);
return;
}
{
json_t *val;
const char *name;
if (NULL != json_object_get (rsc->json_attest,
name))
continue; /* duplicate */
json_array_foreach (rsc->details, idx, str)
json_object_foreach (attrs, name, val)
{
if (0 == strcmp (json_string_value (str),
name))
bool requested = false;
size_t idx;
json_t *str;
if (NULL != json_object_get (rsc->json_attest,
name))
continue; /* duplicate */
json_array_foreach (rsc->details, idx, str)
{
requested = true;
break;
if (0 == strcmp (json_string_value (str),
name))
{
requested = true;
break;
}
}
if (! requested)
continue;
match = true;
GNUNET_assert (0 ==
json_object_set (rsc->json_attest, /* NOT set_new! */
name,
val));
}
if (! requested)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Skipping attribute `%s': not requested\n",
name);
continue;
}
match = true;
GNUNET_assert (0 ==
json_object_set (rsc->json_attest, /* NOT set_new! */
name,
val));
}
json_decref (attrs);
if (! match)
return;
rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
rsc->etime = GNUNET_TIME_timestamp_min (etime,
rsc->etime);
}
@ -248,9 +241,9 @@ reserve_attest_transaction (void *cls,
struct ReserveAttestContext *rsc = cls;
enum GNUNET_DB_QueryStatus qs;
rsc->json_attest = json_object ();
rsc->json_attest = json_array ();
GNUNET_assert (NULL != rsc->json_attest);
qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
&rsc->h_payto,
&kyc_process_cb,
rsc);
@ -262,7 +255,7 @@ reserve_attest_transaction (void *cls,
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_kyc_attributes");
"iterate_kyc_reference");
return qs;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
@ -380,8 +373,16 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
args[0]);
}
return reply_reserve_attest_success (rc->connection,
&rsc);
if (TALER_EC_NONE != rsc.ec)
{
json_decref (rsc.json_attest);
return TALER_MHD_reply_with_ec (rc->connection,
rsc.ec,
NULL);
}
mhd_ret = reply_reserve_attest_success (rc->connection,
&rsc);
return mhd_ret;
}

View File

@ -178,13 +178,14 @@ reserve_close_transaction (void *cls,
{
struct ReserveCloseContext *rcc = cls;
enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount balance;
char *payto_uri = NULL;
const struct TALER_WireFeeSet *wf;
qs = TEH_plugin->select_reserve_close_info (
TEH_plugin->cls,
rcc->reserve_pub,
&rcc->balance,
&balance,
&payto_uri);
switch (qs)
{
@ -225,34 +226,19 @@ reserve_close_transaction (void *cls,
(0 != strcmp (payto_uri,
rcc->payto_uri)) ) )
{
/* KYC check may be needed: we're not returning
the money to the account that funded the reserve
in the first place. */
char *kyc_needed;
const char *kyc_needed;
TALER_payto_hash (rcc->payto_uri,
&rcc->kyc_payto);
rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
qs = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
&rcc->kyc_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_it,
rcc,
&kyc_needed);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"iterate_reserve_close_info");
return qs;
}
kyc_needed
= TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
&rcc->kyc_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_it,
rcc);
if (rcc->qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs)
@ -265,26 +251,12 @@ reserve_close_transaction (void *cls,
"iterate_reserve_close_info");
return qs;
}
if (NULL != kyc_needed)
{
rcc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_needed,
&rcc->kyc_payto,
&rcc->kyc.requirement_row);
GNUNET_free (kyc_needed);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
}
return qs;
}
rcc->kyc.ok = false;
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_needed,
&rcc->kyc_payto,
&rcc->kyc.requirement_row);
}
rcc->kyc.ok = true;
@ -312,7 +284,7 @@ reserve_close_transaction (void *cls,
if (0 >
TALER_amount_subtract (&rcc->wire_amount,
&rcc->balance,
&balance,
&wf->closing))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -330,7 +302,7 @@ reserve_close_transaction (void *cls,
payto_uri,
&rcc->reserve_sig,
rcc->timestamp,
&rcc->balance,
&balance,
&wf->closing);
GNUNET_free (payto_uri);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2023 Taler Systems SA
Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@ -62,16 +62,6 @@ struct ReservePoller
*/
struct GNUNET_TIME_Absolute timeout;
/**
* Public key of the reserve the inquiry is about.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Balance of the reserve, set in the callback.
*/
struct TALER_Amount balance;
/**
* True if we are still suspended.
*/
@ -94,10 +84,13 @@ static struct ReservePoller *rp_tail;
void
TEH_reserves_get_cleanup ()
{
for (struct ReservePoller *rp = rp_head;
NULL != rp;
rp = rp->next)
struct ReservePoller *rp;
while (NULL != (rp = rp_head))
{
GNUNET_CONTAINER_DLL_remove (rp_head,
rp_tail,
rp);
if (rp->suspended)
{
rp->suspended = false;
@ -122,14 +115,11 @@ rp_cleanup (struct TEH_RequestContext *rc)
if (NULL != rp->eh)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
"Cancelling DB event listening\n");
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
rp->eh);
rp->eh = NULL;
}
GNUNET_CONTAINER_DLL_remove (rp_head,
rp_tail,
rp);
GNUNET_free (rp);
}
@ -147,15 +137,26 @@ db_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
struct ReservePoller *rp = cls;
struct TEH_RequestContext *rc = cls;
struct ReservePoller *rp = rc->rh_ctx;
struct GNUNET_AsyncScopeSave old_scope;
(void) extra;
(void) extra_size;
if (NULL == rp)
return; /* event triggered while main transaction
was still running */
if (! rp->suspended)
return; /* might get multiple wake-up events */
TEH_check_invariants ();
rp->suspended = false;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming from long-polling on reserve\n");
TEH_check_invariants ();
GNUNET_CONTAINER_DLL_remove (rp_head,
rp_tail,
rp);
MHD_resume_connection (rp->connection);
TALER_MHD_daemon_trigger ();
TEH_check_invariants ();
@ -163,133 +164,191 @@ db_event_cb (void *cls,
}
/**
* Closure for #reserve_history_transaction.
*/
struct ReserveHistoryContext
{
/**
* Public key of the reserve the inquiry is about.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
* Balance of the reserve, set in the callback.
*/
struct TALER_Amount balance;
/**
* Set to true if we did not find the reserve.
*/
bool not_found;
};
/**
* Function implementing /reserves/ GET transaction.
* Execute a /reserves/ GET. Given the public key of a reserve,
* return the associated transaction history. Runs the
* transaction logic; IF it returns a non-error code, the transaction
* logic MUST NOT queue a MHD response. IF it returns an hard error,
* the transaction logic MUST queue a MHD response and set @a mhd_ret.
* IF it returns the soft error code, the function MAY be called again
* to retry and MUST not queue a MHD response.
*
* @param cls a `struct ReserveHistoryContext *`
* @param connection MHD request which triggered the transaction
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
reserve_balance_transaction (void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
struct ReserveHistoryContext *rsc = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
&rsc->reserve_pub,
&rsc->balance);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_reserve_balance");
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
rsc->not_found = true;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
rsc->not_found = false;
return qs;
}
MHD_RESULT
TEH_handler_reserves_get (struct TEH_RequestContext *rc,
const char *const args[1])
{
struct ReservePoller *rp = rc->rh_ctx;
struct ReserveHistoryContext rsc;
struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
struct GNUNET_DB_EventHandler *eh = NULL;
if (NULL == rp)
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&rsc.reserve_pub,
sizeof (rsc.reserve_pub)))
{
struct GNUNET_TIME_Relative timeout
= GNUNET_TIME_UNIT_ZERO;
rp = GNUNET_new (struct ReservePoller);
rp->connection = rc->connection;
rc->rh_ctx = rp;
rc->rh_cleaner = &rp_cleanup;
GNUNET_CONTAINER_DLL_insert (rp_head,
rp_tail,
rp);
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
&rp->reserve_pub,
sizeof (rp->reserve_pub)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
args[0]);
}
{
const char *long_poll_timeout_ms;
long_poll_timeout_ms
= MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if (NULL != long_poll_timeout_ms)
{
unsigned int timeout_ms;
char dummy;
if (1 != sscanf (long_poll_timeout_ms,
"%u%c",
&timeout_ms,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"timeout_ms (must be non-negative number)");
}
timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
timeout_ms);
}
}
rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
args[0]);
}
{
const char *long_poll_timeout_ms;
if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
(NULL == rp->eh) )
long_poll_timeout_ms
= MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"timeout_ms");
if (NULL != long_poll_timeout_ms)
{
unsigned int timeout_ms;
char dummy;
if (1 != sscanf (long_poll_timeout_ms,
"%u%c",
&timeout_ms,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"timeout_ms (must be non-negative number)");
}
timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
timeout_ms);
}
}
if ( (! GNUNET_TIME_relative_is_zero (timeout)) &&
(NULL == rc->rh_ctx) )
{
struct TALER_ReserveEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
.reserve_pub = rp->reserve_pub
.reserve_pub = rsc.reserve_pub
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting DB event listening until %s\n",
GNUNET_TIME_absolute2s (rp->timeout));
rp->eh = TEH_plugin->event_listen (
TEH_plugin->cls,
GNUNET_TIME_absolute_get_remaining (rp->timeout),
&rep.header,
&db_event_cb,
rp);
"Starting DB event listening\n");
eh = TEH_plugin->event_listen (TEH_plugin->cls,
timeout,
&rep.header,
&db_event_cb,
rc);
}
{
enum GNUNET_DB_QueryStatus qs;
MHD_RESULT mhd_ret;
qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
&rp->reserve_pub,
&rp->balance);
switch (qs)
if (GNUNET_OK !=
TEH_DB_run_transaction (rc->connection,
"get reserve balance",
TEH_MT_REQUEST_OTHER,
&mhd_ret,
&reserve_balance_transaction,
&rsc))
{
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0); /* single-shot query should never have soft-errors */
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
"get_reserve_balance");
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_reserve_balance");
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got reserve balance of %s\n",
TALER_amount2s (&rp->balance));
return TALER_MHD_REPLY_JSON_PACK (rc->connection,
MHD_HTTP_OK,
TALER_JSON_pack_amount ("balance",
&rp->balance));
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
if (! GNUNET_TIME_absolute_is_future (rp->timeout))
{
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
args[0]);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Long-polling on reserve for %s\n",
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_remaining (rp->timeout),
true));
rp->suspended = true;
MHD_suspend_connection (rc->connection);
return MHD_YES;
if (NULL != eh)
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
eh);
return mhd_ret;
}
}
GNUNET_break (0);
return MHD_NO;
/* generate proper response */
if (rsc.not_found)
{
struct ReservePoller *rp = rc->rh_ctx;
if ( (NULL != rp) ||
(GNUNET_TIME_relative_is_zero (timeout)) )
{
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
args[0]);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Long-polling on reserve for %s\n",
GNUNET_STRINGS_relative_time_to_string (timeout,
GNUNET_YES));
rp = GNUNET_new (struct ReservePoller);
rp->connection = rc->connection;
rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
rp->eh = eh;
rc->rh_ctx = rp;
rc->rh_cleaner = &rp_cleanup;
rp->suspended = true;
GNUNET_CONTAINER_DLL_insert (rp_head,
rp_tail,
rp);
MHD_suspend_connection (rc->connection);
return MHD_YES;
}
if (NULL != eh)
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
eh);
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
TALER_JSON_pack_amount ("balance",
&rsc.balance));
}

View File

@ -50,6 +50,11 @@ struct ReserveAttestContext
*/
json_t *attributes;
/**
* Error code encountered in interaction with KYC provider.
*/
enum TALER_ErrorCode ec;
/**
* Set to true if we did not find the reserve.
*/
@ -62,55 +67,53 @@ struct ReserveAttestContext
* legitimization processes for the given user.
*
* @param cls our `struct ReserveAttestContext *`
* @param h_payto account for which the attribute data is stored
* @param provider_section provider that must be checked
* @param birthdate birthdate of user, in format YYYY-MM-DD; can be NULL;
* digits can be 0 if exact day, month or year are unknown
* @param collection_time when was the data collected
* @param expiration_time when does the data expire
* @param enc_attributes_size number of bytes in @a enc_attributes
* @param enc_attributes encrypted attribute data
* @param provider_section KYC provider configuration section
* @param provider_user_id UID at a provider (can be NULL)
* @param legi_id legitimization process ID (can be NULL)
*/
static void
kyc_process_cb (void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *provider_section,
const char *birthdate,
struct GNUNET_TIME_Timestamp collection_time,
struct GNUNET_TIME_Timestamp expiration_time,
size_t enc_attributes_size,
const void *enc_attributes)
const char *provider_user_id,
const char *legi_id)
{
struct ReserveAttestContext *rsc = cls;
struct GNUNET_TIME_Timestamp etime;
json_t *attrs;
json_t *val;
const char *name;
if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
provider_user_id,
legi_id,
&etime,
&attrs);
if (TALER_EC_NONE != rsc->ec)
return;
attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
enc_attributes,
enc_attributes_size);
json_object_foreach (attrs, name, val)
{
bool duplicate = false;
size_t idx;
json_t *str;
json_array_foreach (rsc->attributes, idx, str)
{
json_t *val;
const char *name;
json_object_foreach (attrs, name, val)
{
if (0 == strcmp (json_string_value (str),
name))
bool duplicate = false;
size_t idx;
json_t *str;
json_array_foreach (rsc->attributes, idx, str)
{
duplicate = true;
break;
if (0 == strcmp (json_string_value (str),
name))
{
duplicate = true;
break;
}
}
if (duplicate)
continue;
GNUNET_assert (0 ==
json_array_append (rsc->attributes,
json_string (name)));
}
if (duplicate)
continue;
GNUNET_assert (0 ==
json_array_append (rsc->attributes,
json_string (name)));
}
}
@ -141,7 +144,7 @@ reserve_attest_transaction (void *cls,
rsc->attributes = json_array ();
GNUNET_assert (NULL != rsc->attributes);
qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
&rsc->h_payto,
&kyc_process_cb,
rsc);
@ -210,7 +213,6 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
&rsc))
{
json_decref (rsc.attributes);
rsc.attributes = NULL;
return mhd_ret;
}
}
@ -218,17 +220,23 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
if (rsc.not_found)
{
json_decref (rsc.attributes);
rsc.attributes = NULL;
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
args[0]);
}
if (TALER_EC_NONE != rsc.ec)
{
json_decref (rsc.attributes);
return TALER_MHD_reply_with_ec (rc->connection,
rsc.ec,
NULL);
}
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_array_steal ("details",
rsc.attributes));
GNUNET_JSON_pack_object_steal ("attributes",
rsc.attributes));
}

View File

@ -189,47 +189,24 @@ purse_transaction (void *cls,
{
struct ReservePurseContext *rpc = cls;
enum GNUNET_DB_QueryStatus qs;
char *required;
qs = TALER_KYCLOGIC_kyc_test_required (
const char *required;
required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
&rpc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&amount_iterator,
rpc,
&required);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
return GNUNET_DB_STATUS_HARD_ERROR;
}
rpc);
if (NULL != required)
{
rpc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
required,
&rpc->h_payto,
&rpc->kyc.requirement_row);
GNUNET_free (required);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret
= TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
}
return qs;
}
rpc->kyc.ok = true;
@ -253,7 +230,8 @@ purse_transaction (void *cls,
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (0);
TALER_LOG_WARNING (
"Failed to store purse purse information in database\n");
*mhd_ret =
TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2023 Taler Systems SA
Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@ -1142,29 +1142,4 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
}
MHD_RESULT
TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
enum TALER_AmlDecisionState status)
{
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
switch (status)
{
case TALER_AML_NORMAL:
GNUNET_break (0);
return MHD_NO;
case TALER_AML_PENDING:
ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING;
break;
case TALER_AML_FROZEN:
ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN;
break;
}
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
TALER_JSON_pack_ec (ec));
}
/* end of taler-exchange-httpd_responses.c */

View File

@ -91,19 +91,6 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_KycStatus *kyc);
/**
* Send information that an AML process is blocking
* the operation right now.
*
* @param connection connection to the client
* @param status current AML status
* @return MHD result code
*/
MHD_RESULT
TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
enum TALER_AmlDecisionState status);
/**
* Send assertion that the given denomination key hash
* is not usable (typically expired) at this time.

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014-2023 Taler Systems SA
Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@ -61,21 +61,16 @@ struct WithdrawContext
struct TALER_EXCHANGEDB_KycStatus kyc;
/**
* Hash of the payto-URI representing the account
* from which the money was put into the reserve.
* Hash of the payto-URI representing the reserve
* from which we are withdrawing.
*/
struct TALER_PaytoHashP h_account_payto;
struct TALER_PaytoHashP h_payto;
/**
* Current time for the DB transaction.
*/
struct GNUNET_TIME_Timestamp now;
/**
* AML decision, #TALER_AML_NORMAL if we may proceed.
*/
enum TALER_AmlDecisionState aml_decision;
};
@ -113,7 +108,7 @@ withdraw_amount_cb (void *cls,
return;
qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
TEH_plugin->cls,
&wc->h_account_payto,
&wc->h_payto,
limit,
cb,
cb_cls);
@ -125,34 +120,6 @@ withdraw_amount_cb (void *cls,
}
/**
* Function called on each @a amount that was found to
* be relevant for the AML check as it was merged into
* the reserve.
*
* @param cls `struct TALER_Amount *` to total up the amounts
* @param amount encountered transaction amount
* @param date when was the amount encountered
* @return #GNUNET_OK to continue to iterate,
* #GNUNET_NO to abort iteration
* #GNUNET_SYSERR on internal error (also abort itaration)
*/
static enum GNUNET_GenericReturnValue
aml_amount_cb (
void *cls,
const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute date)
{
struct TALER_Amount *total = cls;
GNUNET_assert (0 <=
TALER_amount_add (total,
total,
amount));
return GNUNET_OK;
}
/**
* Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction
@ -183,153 +150,36 @@ withdraw_transaction (void *cls,
uint64_t ruuid;
const struct TALER_CsNonce *nonce;
const struct TALER_BlindedPlanchet *bp;
struct TALER_PaytoHashP reserve_h_payto;
wc->now = GNUNET_TIME_timestamp_get ();
/* Do AML check: compute total merged amount and check
against applicable AML threshold */
{
char *reserve_payto;
reserve_payto = TALER_reserve_make_payto (TEH_base_url,
&wc->collectable.reserve_pub);
TALER_payto_hash (reserve_payto,
&reserve_h_payto);
GNUNET_free (reserve_payto);
}
{
struct TALER_Amount merge_amount;
struct TALER_Amount threshold;
struct GNUNET_TIME_Absolute now_minus_one_month;
now_minus_one_month
= GNUNET_TIME_absolute_subtract (wc->now.abs_time,
GNUNET_TIME_UNIT_MONTHS);
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TEH_currency,
&merge_amount));
qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
&reserve_h_payto,
now_minus_one_month,
&aml_amount_cb,
&merge_amount);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_merge_amounts_for_kyc_check");
return qs;
}
qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
&reserve_h_payto,
&wc->aml_decision,
&wc->kyc,
&threshold);
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_aml_threshold");
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
threshold = TEH_aml_threshold; /* use default */
wc->aml_decision = TALER_AML_NORMAL;
}
switch (wc->aml_decision)
{
case TALER_AML_NORMAL:
if (0 >= TALER_amount_cmp (&merge_amount,
&threshold))
{
/* merge_amount <= threshold, continue withdraw below */
break;
}
wc->aml_decision = TALER_AML_PENDING;
qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
&reserve_h_payto,
&merge_amount);
if (qs <= 0)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"trigger_aml_process");
return qs;
}
return qs;
case TALER_AML_PENDING:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"AML already pending, doing nothing\n");
return qs;
case TALER_AML_FROZEN:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Account frozen, doing nothing\n");
return qs;
}
}
/* Check if the money came from a wire transfer */
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
&wc->collectable.reserve_pub,
&wc->h_account_payto);
&wc->h_payto);
if (qs < 0)
return qs;
/* If no results, reserve was created by merge, in which case no KYC check
is required as the merge already did that. */
/* If no results, reserve was created by merge,
in which case no KYC check is required as the
merge already did that. */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
char *kyc_required;
const char *kyc_required;
qs = TALER_KYCLOGIC_kyc_test_required (
kyc_required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
&wc->h_account_payto,
&wc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&withdraw_amount_cb,
wc,
&kyc_required);
if (qs < 0)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"kyc_test_required");
}
return qs;
}
wc);
if (NULL != kyc_required)
{
/* insert KYC requirement into DB! */
wc->kyc.ok = false;
qs = TEH_plugin->insert_kyc_requirement_for_account (
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_required,
&wc->h_account_payto,
&wc->h_payto,
&wc->kyc.requirement_row);
GNUNET_free (kyc_required);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_requirement_for_account");
}
return qs;
}
}
wc->kyc.ok = true;
@ -348,13 +198,10 @@ withdraw_transaction (void *cls,
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"do_withdraw");
}
return qs;
}
if (! found)
@ -652,13 +499,8 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
if (! wc.kyc.ok)
return TEH_RESPONSE_reply_kyc_required (rc->connection,
&wc.h_account_payto,
&wc.h_payto,
&wc.kyc);
if (TALER_AML_NORMAL != wc.aml_decision)
return TEH_RESPONSE_reply_aml_blocked (rc->connection,
wc.aml_decision);
{
MHD_RESULT ret;

View File

@ -57,12 +57,6 @@ static struct TALER_BANK_CreditHistoryHandle *hh;
*/
static bool hh_returned_data;
/**
* Set to true if the request for history did not
* succeed because the account was unknown.
*/
static bool hh_account_404;
/**
* When did we start the last @e hh request?
*/
@ -478,9 +472,9 @@ transaction_completed (void)
GNUNET_SCHEDULER_shutdown ();
return;
}
if (! (hh_returned_data || hh_account_404) )
if (! hh_returned_data)
{
/* Enforce long-polling delay even if the server ignored it
/* Enforce long polling delay even if the server ignored it
and returned earlier */
struct GNUNET_TIME_Relative latency;
struct GNUNET_TIME_Relative left;
@ -488,17 +482,8 @@ transaction_completed (void)
latency = GNUNET_TIME_absolute_get_duration (hh_start_time);
left = GNUNET_TIME_relative_subtract (longpoll_timeout,
latency);
if (! (test_mode ||
GNUNET_TIME_relative_is_zero (left)) )
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, // WARNING,
"Server did not respect long-polling, enforcing client-side by sleeping for %s\n",
GNUNET_TIME_relative2s (left,
true));
delayed_until = GNUNET_TIME_relative_to_absolute (left);
}
if (hh_account_404)
delayed_until = GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_MILLISECONDS);
if (test_mode)
delayed_until = GNUNET_TIME_UNIT_ZERO_ABS;
GNUNET_assert (NULL == task);
@ -510,19 +495,402 @@ transaction_completed (void)
* We got incoming transaction details from the bank. Add them
* to the database.
*
* @param wrap_size desired bulk insert size
* @param details array of transaction details
* @param details_length length of the @a details array
*/
static void
process_reply (unsigned int wrap_size,
const struct TALER_BANK_CreditDetails *details,
process_reply (const struct TALER_BANK_CreditDetails *details,
unsigned int details_length)
{
enum GNUNET_DB_QueryStatus qs;
bool shard_done;
uint64_t lroff = latest_row_off;
if (0 == details_length)
{
/* Server should have used 204, not 200! */
GNUNET_break_op (0);
transaction_completed ();
return;
}
hh_returned_data = true;
/* check serial IDs for range constraints */
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
if (cd->serial_id < lroff)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Serial ID %llu not monotonic (got %llu before). Failing!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) lroff);
db_plugin->rollback (db_plugin->cls);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (cd->serial_id > shard_end)
{
/* we are *past* the current shard (likely because the serial_id of the
shard_end happens to not exist in the DB). So commit and stop this
iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serial ID %llu past shard end at %llu, ending iteration early!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) shard_end);
details_length = i;
progress = true;
lroff = cd->serial_id - 1;
break;
}
lroff = cd->serial_id;
}
if (GNUNET_OK !=
db_plugin->start_read_committed (db_plugin->cls,
"wirewatch check for incoming wire transfers"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
started_transaction = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Importing %u transactions\n",
details_length);
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
/* FIXME #7276: Consider using Postgres multi-valued insert here,
for up to 15x speed-up according to
https://dba.stackexchange.com/questions/224989/multi-row-insert-vs-transactional-single-row-inserts#225006
(Note: this may require changing both the
plugin API as well as modifying how this function is called.) */
qs = db_plugin->reserves_in_insert (db_plugin->cls,
&cd->reserve_pub,
&cd->amount,
cd->execution_date,
cd->debit_account_uri,
ai->section_name,
cd->serial_id);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for reserves_in_insert. Rolling back.\n");
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* Either wirewatch was freshly started after the system was
shutdown and we're going over an incomplete shard again
after being restarted, or the shard lock period was too
short (number of workers set incorrectly?) and a 2nd
wirewatcher has been stealing our work while we are still
at it. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Attempted to import transaction %llu (%s) twice. "
"This should happen rarely (if not, ask for support).\n",
(unsigned long long) cd->serial_id,
job_name);
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Imported transaction %llu.",
(unsigned long long) cd->serial_id);
/* normal case */
progress = true;
break;
}
}
latest_row_off = lroff;
shard_done = (shard_end <= latest_row_off);
if (shard_done)
{
/* shard is complete, mark this as well */
qs = db_plugin->complete_shard (db_plugin->cls,
job_name,
shard_start,
shard_end);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for complete_shard. Rolling back.\n");
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
/* Not expected, but let's just continue */
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* normal case */
progress = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Completed shard %s (%llu,%llu] after %s\n",
job_name,
(unsigned long long) shard_start,
(unsigned long long) shard_end,
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_duration (shard_start_time),
true));
break;
}
}
if (! progress)
{
db_plugin->rollback (db_plugin->cls);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Committing %s progress (%llu,%llu] at %llu\n (%s)",
job_name,
(unsigned long long) shard_start,
(unsigned long long) shard_end,
(unsigned long long) latest_row_off,
shard_done
? "shard done"
: "shard incomplete");
qs = db_plugin->commit (db_plugin->cls);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* reduce transaction size to reduce rollback probability */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error on commit. Reducing transaction size.\n");
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
started_transaction = false;
/* normal case */
break;
}
}
if (shard_done)
{
shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time);
shard_open = false;
transaction_completed ();
return;
}
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
* We got incoming transaction details from the bank. Add them
* to the database.
*
* @param details array of transaction details
* @param details_length length of the @a details array
*/
static void
process_reply_batched (const struct TALER_BANK_CreditDetails *details,
unsigned int details_length)
{
enum GNUNET_DB_QueryStatus qs;
bool shard_done;
uint64_t lroff = latest_row_off;
if (0 == details_length)
{
/* Server should have used 204, not 200! */
GNUNET_break_op (0);
transaction_completed ();
return;
}
/* check serial IDs for range constraints */
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
if (cd->serial_id < lroff)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Serial ID %llu not monotonic (got %llu before). Failing!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) lroff);
db_plugin->rollback (db_plugin->cls);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (cd->serial_id > shard_end)
{
/* we are *past* the current shard (likely because the serial_id of the
shard_end happens to not exist in the DB). So commit and stop this
iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serial ID %llu past shard end at %llu, ending iteration early!\n",
(unsigned long long) cd->serial_id,
(unsigned long long) shard_end);
details_length = i;
progress = true;
lroff = cd->serial_id - 1;
break;
}
lroff = cd->serial_id;
}
if (0 != details_length)
{
enum GNUNET_DB_QueryStatus qss[details_length];
struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
hh_returned_data = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Importing %u transactions\n",
details_length);
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
struct TALER_EXCHANGEDB_ReserveInInfo *res = &reserves[i];
res->reserve_pub = &cd->reserve_pub;
res->balance = &cd->amount;
res->execution_time = cd->execution_date;
res->sender_account_details = cd->debit_account_uri;
res->exchange_account_name = ai->section_name;
res->wire_reference = cd->serial_id;
}
qs = db_plugin->batch_reserves_in_insert (db_plugin->cls,
reserves,
details_length,
qss);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for batch_reserves_in_insert. Rolling back.\n");
handle_soft_error ();
return;
default:
break;
}
for (unsigned int i = 0; i<details_length; i++)
{
const struct TALER_BANK_CreditDetails *cd = &details[i];
switch (qss[i])
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for batch_reserves_in_insert(%u). Rolling back.\n",
i);
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* Either wirewatch was freshly started after the system was
shutdown and we're going over an incomplete shard again
after being restarted, or the shard lock period was too
short (number of workers set incorrectly?) and a 2nd
wirewatcher has been stealing our work while we are still
at it. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Attempted to import transaction %llu (%s) twice. "
"This should happen rarely (if not, ask for support).\n",
(unsigned long long) cd->serial_id,
job_name);
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Imported transaction %llu.",
(unsigned long long) cd->serial_id);
/* normal case */
progress = true;
break;
}
}
}
latest_row_off = lroff;
shard_done = (shard_end <= latest_row_off);
if (shard_done)
{
/* shard is complete, mark this as well */
qs = db_plugin->complete_shard (db_plugin->cls,
job_name,
shard_start,
shard_end);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for complete_shard. Rolling back.\n");
handle_soft_error ();
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_break (0);
/* Not expected, but let's just continue */
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* normal case */
progress = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Completed shard %s (%llu,%llu] after %s\n",
job_name,
(unsigned long long) shard_start,
(unsigned long long) shard_end,
GNUNET_STRINGS_relative_time_to_string (
GNUNET_TIME_absolute_get_duration (shard_start_time),
true));
break;
}
shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time);
shard_open = false;
transaction_completed ();
return;
}
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
* We got incoming transaction details from the bank. Add them
* to the database.
*
* @param batch_size desired batch size
* @param details array of transaction details
* @param details_length length of the @a details array
*/
static void
process_reply_batched2 (unsigned int batch_size,
const struct TALER_BANK_CreditDetails *details,
unsigned int details_length)
{
enum GNUNET_DB_QueryStatus qs;
bool shard_done;
uint64_t lroff = latest_row_off;
if (0 == details_length)
{
/* Server should have used 204, not 200! */
@ -582,11 +950,11 @@ process_reply (unsigned int wrap_size,
res->exchange_account_name = ai->section_name;
res->wire_reference = cd->serial_id;
}
qs = db_plugin->reserves_in_insert (db_plugin->cls,
reserves,
details_length,
wrap_size,
qss);
qs = db_plugin->batch2_reserves_in_insert (db_plugin->cls,
reserves,
details_length,
batch_size,
qss);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@ -596,7 +964,7 @@ process_reply (unsigned int wrap_size,
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got DB soft error for batch2_reserves_in_insert (%u). Rolling back.\n",
wrap_size);
batch_size);
handle_soft_error ();
return;
default:
@ -701,44 +1069,56 @@ static void
history_cb (void *cls,
const struct TALER_BANK_CreditHistoryResponse *reply)
{
static int wrap_size = -2;
static int batch_mode = -2;
(void) cls;
if (-2 == wrap_size)
if (-2 == batch_mode)
{
const char *mode = getenv ("TALER_WIREWATCH_WARP_SIZE");
const char *mode = getenv ("TALER_USE_BATCH");
char dummy;
if ( (NULL == mode) ||
(1 != sscanf (mode,
"%d%c",
&wrap_size,
&batch_mode,
&dummy)) )
{
if (NULL != mode)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Bad batch mode `%s' specified\n",
mode);
wrap_size = 8; /* maximum supported is currently 8 */
batch_mode = -1;
}
}
GNUNET_assert (NULL == task);
hh = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"History request returned with HTTP status %u\n",
reply->http_status);
switch (reply->http_status)
{
case MHD_HTTP_OK:
process_reply (wrap_size,
reply->details.success.details,
reply->details.success.details_length);
switch (batch_mode)
{
case -1:
process_reply (reply->details.success.details,
reply->details.success.details_length);
break;
case 0:
process_reply_batched (reply->details.success.details,
reply->details.success.details_length);
break;
default:
process_reply_batched2 ((unsigned int) batch_mode,
reply->details.success.details,
reply->details.success.details_length);
break;
}
return;
case MHD_HTTP_NO_CONTENT:
transaction_completed ();
return;
case MHD_HTTP_NOT_FOUND:
hh_account_404 = true;
if (ignore_account_404)
{
transaction_completed ();
@ -773,11 +1153,10 @@ continue_with_shard (void *cls)
shard_end - latest_row_off);
GNUNET_assert (NULL == hh);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting credit history starting from %llu\n",
"Requesting credit history staring from %llu\n",
(unsigned long long) latest_row_off);
hh_start_time = GNUNET_TIME_absolute_get ();
hh_returned_data = false;
hh_account_404 = false;
hh = TALER_BANK_credit_history (ctx,
ai->auth,
latest_row_off,
@ -874,17 +1253,6 @@ lock_shard (void *cls)
job_name,
GNUNET_STRINGS_relative_time_to_string (rdelay,
true));
#if 1
if (GNUNET_TIME_relative_cmp (rdelay,
>,
GNUNET_TIME_UNIT_SECONDS))
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Delay would have been for %s\n",
GNUNET_TIME_relative2s (rdelay,
true));
rdelay = GNUNET_TIME_relative_min (rdelay,
GNUNET_TIME_UNIT_SECONDS);
#endif
delayed_until = GNUNET_TIME_relative_to_absolute (rdelay);
}
GNUNET_assert (NULL == task);
@ -897,7 +1265,7 @@ lock_shard (void *cls)
job_name,
GNUNET_STRINGS_relative_time_to_string (
wirewatch_idle_sleep_interval,
true));
GNUNET_YES));
delayed_until = GNUNET_TIME_relative_to_absolute (
wirewatch_idle_sleep_interval);
shard_open = false;

View File

@ -7,7 +7,6 @@ TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
# Currency supported by the exchange (can only be one)
CURRENCY = EUR
CURRENCY_ROUND_UNIT = EUR:0.01
AML_THRESHOLD = EUR:1000000
[auditor]
TINY_AMOUNT = EUR:0.01

View File

@ -1,15 +1,15 @@
test-exchangedb-auditors
test-exchangedb-denomkeys
test-exchangedb-fees
test-exchangedb-postgres
test-exchangedb-signkeys
test-perf-taler-exchangedb
bench-db-postgres
perf_deposits_get_ready-postgres
perf_get_link_data-postgres
perf_reserves_in_insert-postgres
perf_select_refunds_by_coin-postgres
shard-drop0001.sqltest-exchangedb-by-j-postgres
test-exchangedb-by-j-postgres
perf-exchangedb-reserves-in-insert-postgres
exchange-0002.sql
procedures.sql
exchange-0003.sql
perf-exchangedb-reserves-in-insert-postgres
test-exchangedb-batch-reserves-in-insert-postgres
test-exchangedb-by-j-postgres
test-exchangedb-populate-link-data-postgres
test-exchangedb-populate-ready-deposit-postgres
test-exchangedb-populate-select-refunds-by-coin-postgres
test-exchangedb-populate-table-postgres

View File

@ -1,6 +1,6 @@
--
-- This file is part of TALER
-- Copyright (C) 2014--2023 Taler Systems SA
-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@ -116,22 +116,6 @@ BEGIN
',ADD CONSTRAINT ' || table_name || '_coin_pub_merchant_pub_h_contract_terms_key'
' UNIQUE (coin_pub, merchant_pub, h_contract_terms)'
);
EXECUTE FORMAT (
'CREATE INDEX ' || table_name || '_by_ready '
'ON ' || table_name || ' '
'(wire_deadline ASC'
',shard ASC'
',coin_pub'
') WHERE NOT (done OR policy_blocked);'
);
EXECUTE FORMAT (
'CREATE INDEX ' || table_name || '_for_matching '
'ON ' || table_name || ' '
'(refund_deadline ASC'
',merchant_pub'
',coin_pub'
') WHERE NOT (done OR policy_blocked);'
);
END
$$;
@ -415,5 +399,29 @@ INSERT INTO exchange_tables
,'exchange-0002'
,'foreign'
,TRUE
,FALSE)
;
,FALSE),
('deposits_by_ready'
,'exchange-0002'
,'create'
,TRUE
,TRUE),
('deposits_by_ready'
,'exchange-0002'
,'constrain'
,TRUE
,TRUE),
('deposits_for_matching'
,'exchange-0002'
,'create'
,TRUE
,TRUE),
('deposits_for_matching'
,'exchange-0002'
,'constrain'
,TRUE
,TRUE),
('deposits'
,'exchange-0002'
,'master'
,TRUE
,FALSE);

View File

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

View File

@ -62,9 +62,6 @@ BEGIN
,table_name
,partition_suffix
);
--
-- FIXME: Add comment for link_sig
--
PERFORM comment_partitioned_column(
'envelope of the new coin to be signed'
,'coin_ev'

View File

@ -26,14 +26,12 @@ BEGIN
PERFORM create_partitioned_table(
'CREATE TABLE IF NOT EXISTS %I'
'(aml_history_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
',h_payto BYTEA CHECK (LENGTH(h_payto)=32)'
',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
',new_threshold_val INT8 NOT NULL DEFAULT(0)'
',new_threshold_frac INT4 NOT NULL DEFAULT(0)'
',new_status INT4 NOT NULL DEFAULT(0)'
',decision_time INT8 NOT NULL DEFAULT(0)'
',justification VARCHAR NOT NULL'
',kyc_requirements VARCHAR'
',kyc_req_row INT8 NOT NULL DEFAULT(0)'
',decider_pub BYTEA CHECK (LENGTH(decider_pub)=32)'
',decider_sig BYTEA CHECK (LENGTH(decider_sig)=64)'
') %s ;'
@ -82,18 +80,6 @@ BEGIN
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Additional KYC requirements imposed by the AML staff member. Serialized JSON array of strings.'
,'kyc_requirements'
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Row in the KYC table for this KYC requirement, 0 for none.'
,'kyc_req_row'
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Signature key of the staff member affirming the AML decision; of type AML_DECISION'
,'decider_sig'
@ -124,10 +110,11 @@ BEGIN
EXECUTE FORMAT (
'CREATE INDEX ' || table_name || '_main_index '
'ON ' || table_name || ' '
'(h_payto, decision_time DESC);'
'(h_payto ASC, decision_time ASC);'
);
END $$;
-- FIXME: also have INSERT on AML decisions to update AML status!
INSERT INTO exchange_tables
(name

View File

@ -30,7 +30,6 @@ BEGIN
',threshold_val INT8 NOT NULL DEFAULT(0)'
',threshold_frac INT4 NOT NULL DEFAULT(0)'
',status INT4 NOT NULL DEFAULT(0)'
',kyc_requirement INT8 NOT NULL DEFAULT(0)'
') %s ;'
,table_name
,'PARTITION BY HASH (h_payto)'

View File

@ -32,7 +32,7 @@ BEGIN
',birthdate VARCHAR'
',collection_time INT8 NOT NULL'
',expiration_time INT8 NOT NULL'
',encrypted_attributes BYTEA NOT NULL'
',encrypted_attributes VARCHAR NOT NULL'
') %s ;'
,table_name
,'PARTITION BY HASH (h_payto)'

View File

@ -14,25 +14,26 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
CREATE FUNCTION create_table_age_withdraw_commitments(
CREATE FUNCTION create_table_withdraw_age_commitments(
IN partition_suffix VARCHAR DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_commitments';
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
BEGIN
PERFORM create_partitioned_table(
'CREATE TABLE %I'
'(age_withdraw_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
',h_commitment BYTEA CHECK (LENGTH(h_commitment)=64)'
'(withdraw_age_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
',h_commitment BYTEA PRIMARY KEY CHECK (LENGTH(h_commitment)=64)'
',amount_with_fee_val INT8 NOT NULL'
',amount_with_fee_frac INT4 NOT NULL'
',max_age INT2 NOT NULL'
',reserve_pub BYTEA CHECK (LENGTH(reserve_pub)=32)'
',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
',max_age_group INT2 NOT NULL'
',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
',reserve_sig BYTEA CHECK (LENGTH(reserve_sig)=64)'
',noreveal_index INT4 NOT NULL'
',timestamp INT8 NOT NULL'
') %s ;'
,table_name
,'PARTITION BY HASH (reserve_pub)'
@ -50,8 +51,8 @@ BEGIN
,partition_suffix
);
PERFORM comment_partitioned_column(
'The maximum age (in years) that the client commits to with this request'
,'max_age'
'The maximum age group that the client commits to with this request'
,'max_age_group'
,table_name
,partition_suffix
);
@ -73,62 +74,76 @@ BEGIN
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Timestamp with the time when the withdraw-age request was received by the exchange'
,'timestamp'
,table_name
,partition_suffix
);
END
$$;
CREATE FUNCTION constrain_table_age_withdraw_commitments(
CREATE FUNCTION constrain_table_withdraw_age_commitments(
IN partition_suffix VARCHAR
)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_commitments';
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
BEGIN
table_name = concat_ws('_', table_name, partition_suffix);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD PRIMARY KEY (h_commitment);'
' ADD PRIMARY KEY (h_commitment, reserve_pub);'
);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_h_commitment_reserve_pub_key'
' UNIQUE (h_commitment, reserve_pub);'
);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_age_withdraw_commitment_id_key'
' UNIQUE (age_withdraw_commitment_id);'
' ADD CONSTRAINT ' || table_name || '_withdraw_age_commitment_id_key'
' UNIQUE (withdraw_age_commitment_id);'
);
END
$$;
CREATE FUNCTION foreign_table_age_withdraw_commitments()
CREATE FUNCTION foreign_table_withdraw_age_commitments()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_commitments';
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
BEGIN
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
' FOREIGN KEY (reserve_pub)'
' REFERENCES reserves(reserve_pub) ON DELETE CASCADE;'
' REFERENCES reserves (reserve_pub) ON DELETE CASCADE;'
);
END
$$;
INSERT INTO exchange_tables
(name
,version
,action
,partitioned
,by_range)
VALUES
('age_withdraw_commitments', 'exchange-0003', 'create', TRUE ,FALSE),
('age_withdraw_commitments', 'exchange-0003', 'constrain',TRUE ,FALSE),
('age_withdraw_commitments', 'exchange-0003', 'foreign', TRUE ,FALSE);
(name
,version
,action
,partitioned
,by_range)
VALUES
('withdraw_age_commitments'
,'exchange-0003'
,'create'
,TRUE
,FALSE),
('withdraw_age_commitments'
,'exchange-0003'
,'constrain'
,TRUE
,FALSE),
('withdraw_age_commitments'
,'exchange-0003'
,'foreign'
,TRUE
,FALSE);

View File

@ -14,24 +14,22 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
CREATE FUNCTION create_table_age_withdraw_revealed_coins(
CREATE FUNCTION create_table_withdraw_age_reveals(
IN partition_suffix VARCHAR DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
BEGIN
PERFORM create_partitioned_table(
'CREATE TABLE %I'
'(age_withdraw_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE
',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=64)'
'(withdraw_age_reveals_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE
',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=32)'
',freshcoin_index INT4 NOT NULL'
',denominations_serial INT8 NOT NULL'
',coin_ev BYTEA NOT NULL'
',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=64)'
',ev_sig BYTEA NOT NULL'
',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=32)'
') %s ;'
,table_name
,'PARTITION BY HASH (h_commitment)'
@ -61,41 +59,29 @@ BEGIN
,partition_suffix
);
PERFORM comment_partitioned_column(
'Envelope of the new coin to be signed'
,'coin_ev'
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Hash of the envelope of the new coin to be signed (for lookups)'
'Hash of the blinded coins'
,'h_coin_ev'
,table_name
,partition_suffix
);
PERFORM comment_partitioned_column(
'Exchange signature over the envelope'
,'ev_sig'
,table_name
,partition_suffix
);
END
$$;
CREATE FUNCTION constrain_table_age_withdraw_revealed_coins(
CREATE FUNCTION constrain_table_withdraw_age_reveals(
IN partition_suffix VARCHAR
)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
BEGIN
table_name = concat_ws('_', table_name, partition_suffix);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_age_withdraw_revealed_coins_id_key'
' UNIQUE (age_withdraw_revealed_coins_id);'
' ADD CONSTRAINT ' || table_name || '_withdraw_age_reveals_id_key'
' UNIQUE (withdraw_age_reveals_id);'
);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
@ -105,18 +91,18 @@ BEGIN
END
$$;
CREATE FUNCTION foreign_table_age_withdraw_revealed_coins()
CREATE FUNCTION foreign_table_withdraw_age_reveals()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
BEGIN
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
' ADD CONSTRAINT ' || table_name || '_foreign_h_commitment'
' FOREIGN KEY (h_commitment)'
' REFERENCES age_withdraw_commitments (h_commitment) ON DELETE CASCADE;'
' REFERENCES withdraw_age_commitments (h_commitment) ON DELETE CASCADE;'
);
EXECUTE FORMAT (
'ALTER TABLE ' || table_name ||
@ -135,17 +121,17 @@ INSERT INTO exchange_tables
,partitioned
,by_range)
VALUES
('age_withdraw_revealed_coins'
('withdraw_age_reveals'
,'exchange-0003'
,'create'
,TRUE
,FALSE),
('age_withdraw_revealed_coins'
('withdraw_age_reveals'
,'exchange-0003'
,'constrain'
,TRUE
,FALSE),
('age_withdraw_revealed_coins'
('withdraw_age_reveals'
,'exchange-0003'
,'foreign'
,TRUE

View File

@ -96,7 +96,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_get_drain_profit.h pg_get_drain_profit.c \
pg_get_purse_deposit.h pg_get_purse_deposit.c \
pg_insert_contract.h pg_insert_contract.c \
pg_select_aml_threshold.h pg_select_aml_threshold.c \
pg_select_contract.h pg_select_contract.c \
pg_select_purse_merge.h pg_select_purse_merge.c \
pg_select_contract_by_purse.h pg_select_contract_by_purse.c \
@ -115,8 +114,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_drain_kyc_alert.h pg_drain_kyc_alert.c \
pg_reserves_in_insert.h pg_reserves_in_insert.c \
pg_get_withdraw_info.h pg_get_withdraw_info.c \
pg_get_age_withdraw_info.c pg_get_age_withdraw_info.h \
pg_batch_ensure_coin_known.h pg_batch_ensure_coin_known.c \
pg_do_batch_withdraw.h pg_do_batch_withdraw.c \
pg_get_policy_details.h pg_get_policy_details.c \
pg_persist_policy_details.h pg_persist_policy_details.c \
@ -139,7 +136,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \
pg_select_kyc_attributes.h pg_select_kyc_attributes.c \
pg_insert_aml_officer.h pg_insert_aml_officer.c \
pg_test_aml_officer.h pg_test_aml_officer.c \
pg_update_aml_officer.h pg_update_aml_officer.c \
pg_lookup_aml_officer.h pg_lookup_aml_officer.c \
pg_trigger_aml_process.h pg_trigger_aml_process.c \
pg_select_aml_process.h pg_select_aml_process.c \
@ -258,6 +255,8 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_select_purse_deposits_above_serial_id.h pg_select_purse_deposits_above_serial_id.c \
pg_select_account_merges_above_serial_id.h pg_select_account_merges_above_serial_id.c \
pg_select_all_purse_decisions_above_serial_id.h pg_select_all_purse_decisions_above_serial_id.c \
pg_batch_reserves_in_insert.h pg_batch_reserves_in_insert.c \
pg_batch2_reserves_in_insert.h pg_batch2_reserves_in_insert.c \
pg_select_reserve_open_above_serial_id.c pg_select_reserve_open_above_serial_id.h
libtaler_plugin_exchangedb_postgres_la_LIBADD = \
$(LTLIBINTL)
@ -292,19 +291,24 @@ libtalerexchangedb_la_LDFLAGS = \
check_PROGRAMS = \
test-exchangedb-postgres
noinst_PROGRAMS = \
test-exchangedb-postgres \
bench-db-postgres\
perf_get_link_data-postgres\
perf_select_refunds_by_coin-postgres\
perf_reserves_in_insert-postgres \
perf_deposits_get_ready-postgres
perf-exchangedb-reserves-in-insert-postgres\
test-exchangedb-by-j-postgres\
test-exchangedb-batch-reserves-in-insert-postgres\
test-exchangedb-populate-table-postgres\
test-exchangedb-populate-link-data-postgres\
test-exchangedb-populate-ready-deposit-postgres
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
TESTS = \
$(check_PROGRAMS)
test-exchangedb-postgres\
test-exchangedb-by-j-postgres\
perf-exchangedb-reserves-in-insert-postgres\
test-exchangedb-batch-reserves-in-insert-postgres\
test-exchangedb-populate-table-postgres\
test-exchangedb-populate-link-data-postgres\
test-exchangedb-populate-ready-deposit-postgres
test_exchangedb_postgres_SOURCES = \
test_exchangedb.c
test_exchangedb_postgres_LDADD = \
@ -317,6 +321,32 @@ test_exchangedb_postgres_LDADD = \
-lgnunetutil \
$(XLIB)
test_exchangedb_by_j_postgres_SOURCES = \
test_exchangedb_by_j.c
test_exchangedb_by_j_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \
-ljansson \
-lgnunetjson \
-lgnunetutil \
-lm \
$(XLIB)
perf_exchangedb_reserves_in_insert_postgres_SOURCES = \
perf_exchangedb_reserves_in_insert.c
perf_exchangedb_reserves_in_insert_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \
-ljansson \
-lgnunetjson \
-lgnunetutil \
$(XLIB)
bench_db_postgres_SOURCES = \
bench_db.c
bench_db_postgres_LDADD = \
@ -327,9 +357,21 @@ bench_db_postgres_LDADD = \
-lgnunetutil \
$(XLIB)
perf_reserves_in_insert_postgres_SOURCES = \
perf_reserves_in_insert.c
perf_reserves_in_insert_postgres_LDADD = \
test_exchangedb_batch_reserves_in_insert_postgres_SOURCES = \
test_exchangedb_batch_reserves_in_insert.c
test_exchangedb_batch_reserves_in_insert_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \
-ljansson \
-lgnunetjson \
-lgnunetutil \
$(XLIB)
test_exchangedb_populate_table_postgres_SOURCES = \
test_exchangedb_populate_table.c
test_exchangedb_populate_table_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
@ -340,9 +382,9 @@ perf_reserves_in_insert_postgres_LDADD = \
-lm \
$(XLIB)
perf_select_refunds_by_coin_postgres_SOURCES = \
perf_select_refunds_by_coin.c
perf_select_refunds_by_coin_postgres_LDADD = \
test_exchangedb_populate_link_data_postgres_SOURCES = \
test_exchangedb_populate_link_data.c
test_exchangedb_populate_link_data_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
@ -353,9 +395,9 @@ perf_select_refunds_by_coin_postgres_LDADD = \
-lm \
$(XLIB)
perf_get_link_data_postgres_SOURCES = \
perf_get_link_data.c
perf_get_link_data_postgres_LDADD = \
test_exchangedb_populate_ready_deposit_postgres_SOURCES = \
test_exchangedb_populate_ready_deposit.c
test_exchangedb_populate_ready_deposit_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
@ -366,19 +408,5 @@ perf_get_link_data_postgres_LDADD = \
-lm \
$(XLIB)
perf_deposits_get_ready_postgres_SOURCES = \
perf_deposits_get_ready.c
perf_deposits_get_ready_postgres_LDADD = \
libtalerexchangedb.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/pq/libtalerpq.la \
-ljansson \
-lgnunetjson \
-lgnunetutil \
-lm \
$(XLIB)
EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
libtaler_plugin_exchangedb_postgres.la

View File

@ -25,8 +25,6 @@ SET search_path TO exchange;
#include "0003-aml_status.sql"
#include "0003-aml_staff.sql"
#include "0003-aml_history.sql"
#include "0003-age_withdraw_commitments.sql"
#include "0003-age_withdraw_reveals.sql"
COMMIT;

View File

@ -0,0 +1,184 @@
--
-- 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_batch2_reserves_insert(
IN in_reserve_pub BYTEA,
IN in_expiration_date INT8,
IN in_gc_date INT8,
IN in_wire_ref INT8,
IN in_credit_val INT8,
IN in_credit_frac INT4,
IN in_exchange_account_name VARCHAR,
IN in_exectution_date INT8,
IN in_wire_source_h_payto BYTEA, ---h_payto
IN in_payto_uri VARCHAR,
IN in_reserve_expiration INT8,
IN in_notify text,
IN in2_notify text,
IN in2_reserve_pub BYTEA,
IN in2_wire_ref INT8,
IN in2_credit_val INT8,
IN in2_credit_frac INT4,
IN in2_exchange_account_name VARCHAR,
IN in2_exectution_date INT8,
IN in2_wire_source_h_payto BYTEA, ---h_payto
IN in2_payto_uri VARCHAR,
IN in2_reserve_expiration INT8,
OUT out_reserve_found BOOLEAN,
OUT out_reserve_found2 BOOLEAN,
OUT transaction_duplicate BOOLEAN,
OUT transaction_duplicate2 BOOLEAN,
OUT ruuid INT8,
OUT ruuid2 INT8)
LANGUAGE plpgsql
AS $$
DECLARE
curs_reserve_exist REFCURSOR;
DECLARE
curs_transaction_exist refcursor;
DECLARE
i RECORD;
DECLARE
r RECORD;
DECLARE
k INT8;
BEGIN
transaction_duplicate=TRUE;
transaction_duplicate2=TRUE;
out_reserve_found = TRUE;
out_reserve_found2 = TRUE;
ruuid=0;
ruuid2=0;
k=0;
INSERT INTO wire_targets
(wire_target_h_payto
,payto_uri)
VALUES
(in_wire_source_h_payto
,in_payto_uri),
(in2_wire_source_h_payto
,in2_payto_uri)
ON CONFLICT DO NOTHING;
OPEN curs_reserve_exist FOR
WITH reserve_changes AS (
INSERT INTO reserves
(reserve_pub
,current_balance_val
,current_balance_frac
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_credit_val
,in_credit_frac
,in_expiration_date
,in_gc_date),
(in2_reserve_pub
,in2_credit_val
,in2_credit_frac
,in_expiration_date
,in_gc_date)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid,reserve_pub)
SELECT * FROM reserve_changes;
WHILE k < 2 LOOP
FETCH FROM curs_reserve_exist INTO i;
IF FOUND
THEN
IF in_reserve_pub = i.reserve_pub
THEN
ruuid = i.reserve_uuid;
IF in_reserve_pub <> in2_reserve_pub
THEN
out_reserve_found = FALSE;
END IF;
END IF;
IF in2_reserve_pub = i.reserve_pub
THEN
out_reserve_found2 = FALSE;
ruuid2 = i.reserve_uuid;
END IF;
END IF;
k=k+1;
END LOOP;
CLOSE curs_reserve_exist;
PERFORM pg_notify(in_notify, NULL);
PERFORM pg_notify(in2_notify, NULL);
OPEN curs_transaction_exist FOR
WITH reserve_in_exist AS (
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit_val
,credit_frac
,exchange_account_section
,wire_source_h_payto
,execution_date)
VALUES
(in_reserve_pub
,in_wire_ref
,in_credit_val
,in_credit_frac
,in_exchange_account_name
,in_wire_source_h_payto
,in_expiration_date),
(in2_reserve_pub
,in2_wire_ref
,in2_credit_val
,in2_credit_frac
,in2_exchange_account_name
,in2_wire_source_h_payto
,in_expiration_date)
ON CONFLICT DO NOTHING
RETURNING reserve_pub)
SELECT * FROM reserve_in_exist;
FETCH FROM curs_transaction_exist INTO r;
IF FOUND
THEN
IF in_reserve_pub = r.reserve_pub
THEN
transaction_duplicate = FALSE;
END IF;
IF in2_reserve_pub = r.reserve_pub
THEN
transaction_duplicate2 = FALSE;
END IF;
FETCH FROM curs_transaction_exist INTO r;
IF FOUND
THEN
IF in_reserve_pub = r.reserve_pub
THEN
transaction_duplicate = FALSE;
END IF;
IF in2_reserve_pub = r.reserve_pub
THEN
transaction_duplicate2 = FALSE;
END IF;
END IF;
END IF;
/* IF transaction_duplicate
OR transaction_duplicate2
THEN
CLOSE curs_transaction_exist;
ROLLBACK;
RETURN;
END IF;*/
CLOSE curs_transaction_exist;
RETURN;
END $$;

View File

@ -0,0 +1,284 @@
--
-- 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_batch4_reserves_insert(
IN in_reserve_pub BYTEA,
IN in_expiration_date INT8,
IN in_gc_date INT8,
IN in_wire_ref INT8,
IN in_credit_val INT8,
IN in_credit_frac INT4,
IN in_exchange_account_name VARCHAR,
IN in_exectution_date INT8,
IN in_wire_source_h_payto BYTEA, ---h_payto
IN in_payto_uri VARCHAR,
IN in_reserve_expiration INT8,
IN in_notify text,
IN in2_notify text,
IN in3_notify text,
IN in4_notify text,
IN in2_reserve_pub BYTEA,
IN in2_wire_ref INT8,
IN in2_credit_val INT8,
IN in2_credit_frac INT4,
IN in2_exchange_account_name VARCHAR,
IN in2_exectution_date INT8,
IN in2_wire_source_h_payto BYTEA, ---h_payto
IN in2_payto_uri VARCHAR,
IN in2_reserve_expiration INT8,
IN in3_reserve_pub BYTEA,
IN in3_wire_ref INT8,
IN in3_credit_val INT8,
IN in3_credit_frac INT4,
IN in3_exchange_account_name VARCHAR,
IN in3_exectution_date INT8,
IN in3_wire_source_h_payto BYTEA, ---h_payto
IN in3_payto_uri VARCHAR,
IN in3_reserve_expiration INT8,
IN in4_reserve_pub BYTEA,
IN in4_wire_ref INT8,
IN in4_credit_val INT8,
IN in4_credit_frac INT4,
IN in4_exchange_account_name VARCHAR,
IN in4_exectution_date INT8,
IN in4_wire_source_h_payto BYTEA, ---h_payto
IN in4_payto_uri VARCHAR,
IN in4_reserve_expiration INT8,
OUT out_reserve_found BOOLEAN,
OUT out_reserve_found2 BOOLEAN,
OUT out_reserve_found3 BOOLEAN,
OUT out_reserve_found4 BOOLEAN,
OUT transaction_duplicate BOOLEAN,
OUT transaction_duplicate2 BOOLEAN,
OUT transaction_duplicate3 BOOLEAN,
OUT transaction_duplicate4 BOOLEAN,
OUT ruuid INT8,
OUT ruuid2 INT8,
OUT ruuid3 INT8,
OUT ruuid4 INT8)
LANGUAGE plpgsql
AS $$
DECLARE
curs_reserve_exist refcursor;
DECLARE
k INT8;
DECLARE
curs_transaction_exist refcursor;
DECLARE
i RECORD;
BEGIN
--INITIALIZATION
transaction_duplicate=TRUE;
transaction_duplicate2=TRUE;
transaction_duplicate3=TRUE;
transaction_duplicate4=TRUE;
out_reserve_found = TRUE;
out_reserve_found2 = TRUE;
out_reserve_found3 = TRUE;
out_reserve_found4 = TRUE;
ruuid=0;
ruuid2=0;
ruuid3=0;
ruuid4=0;
k=0;
--SIMPLE INSERT ON CONFLICT DO NOTHING
INSERT INTO wire_targets
(wire_target_h_payto
,payto_uri)
VALUES
(in_wire_source_h_payto
,in_payto_uri),
(in2_wire_source_h_payto
,in2_payto_uri),
(in3_wire_source_h_payto
,in3_payto_uri),
(in4_wire_source_h_payto
,in4_payto_uri)
ON CONFLICT DO NOTHING;
OPEN curs_reserve_exist FOR
WITH reserve_changes AS (
INSERT INTO reserves
(reserve_pub
,current_balance_val
,current_balance_frac
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_credit_val
,in_credit_frac
,in_expiration_date
,in_gc_date),
(in2_reserve_pub
,in2_credit_val
,in2_credit_frac
,in_expiration_date
,in_gc_date),
(in3_reserve_pub
,in3_credit_val
,in3_credit_frac
,in_expiration_date
,in_gc_date),
(in4_reserve_pub
,in4_credit_val
,in4_credit_frac
,in_expiration_date
,in_gc_date)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid,reserve_pub)
SELECT * FROM reserve_changes;
WHILE k < 4 LOOP
FETCH FROM curs_reserve_exist INTO i;
IF FOUND
THEN
IF in_reserve_pub = i.reserve_pub
THEN
ruuid = i.reserve_uuid;
IF in_reserve_pub
NOT IN (in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub)
THEN
out_reserve_found = FALSE;
END IF;
END IF;
IF in2_reserve_pub = i.reserve_pub
THEN
ruuid2 = i.reserve_uuid;
IF in2_reserve_pub
NOT IN (in_reserve_pub
,in3_reserve_pub
,in4_reserve_pub)
THEN
out_reserve_found2 = FALSE;
END IF;
END IF;
IF in3_reserve_pub = i.reserve_pub
THEN
ruuid3 = i.reserve_uuid;
IF in3_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in4_reserve_pub)
THEN
out_reserve_found3 = FALSE;
END IF;
END IF;
IF in4_reserve_pub = i.reserve_pub
THEN
ruuid4 = i.reserve_uuid;
IF in4_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub)
THEN
out_reserve_found4 = FALSE;
END IF;
END IF;
END IF;
k=k+1;
END LOOP;
CLOSE curs_reserve_exist;
PERFORM pg_notify(in_notify, NULL);
PERFORM pg_notify(in2_notify, NULL);
PERFORM pg_notify(in3_notify, NULL);
PERFORM pg_notify(in4_notify, NULL);
k=0;
OPEN curs_transaction_exist FOR
WITH reserve_in_changes AS (
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit_val
,credit_frac
,exchange_account_section
,wire_source_h_payto
,execution_date)
VALUES
(in_reserve_pub
,in_wire_ref
,in_credit_val
,in_credit_frac
,in_exchange_account_name
,in_wire_source_h_payto
,in_expiration_date),
(in2_reserve_pub
,in2_wire_ref
,in2_credit_val
,in2_credit_frac
,in2_exchange_account_name
,in2_wire_source_h_payto
,in_expiration_date),
(in3_reserve_pub
,in3_wire_ref
,in3_credit_val
,in3_credit_frac
,in3_exchange_account_name
,in3_wire_source_h_payto
,in_expiration_date),
(in4_reserve_pub
,in4_wire_ref
,in4_credit_val
,in4_credit_frac
,in4_exchange_account_name
,in4_wire_source_h_payto
,in_expiration_date)
ON CONFLICT DO NOTHING
RETURNING reserve_pub)
SELECT * FROM reserve_in_changes;
WHILE k < 4 LOOP
FETCH FROM curs_transaction_exist INTO i;
IF FOUND
THEN
IF in_reserve_pub = i.reserve_pub
THEN
transaction_duplicate = FALSE;
END IF;
IF in2_reserve_pub = i.reserve_pub
THEN
transaction_duplicate2 = FALSE;
END IF;
IF in3_reserve_pub = i.reserve_pub
THEN
transaction_duplicate3 = FALSE;
END IF;
IF in4_reserve_pub = i.reserve_pub
THEN
transaction_duplicate4 = FALSE;
END IF;
END IF;
k=k+1;
END LOOP;
/**ROLLBACK TRANSACTION IN SORTED PROCEDURE IS IT PROSSIBLE ?**/
/*IF transaction_duplicate
OR transaction_duplicate2
OR transaction_duplicate3
OR transaction_duplicate4
THEN
RAISE EXCEPTION 'Reserve did not exist, but INSERT into reserves_in gave conflict';
ROLLBACK;
CLOSE curs_transaction_exist;
RETURN;
END IF;*/
CLOSE curs_transaction_exist;
RETURN;
END $$;

View File

@ -0,0 +1,506 @@
--
-- 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_batch8_reserves_insert(
IN in_reserve_pub BYTEA,
IN in_expiration_date INT8,
IN in_gc_date INT8,
IN in_wire_ref INT8,
IN in_credit_val INT8,
IN in_credit_frac INT4,
IN in_exchange_account_name VARCHAR,
IN in_exectution_date INT8,
IN in_wire_source_h_payto BYTEA, ---h_payto
IN in_payto_uri VARCHAR,
IN in_reserve_expiration INT8,
IN in_notify text,
IN in2_notify text,
IN in3_notify text,
IN in4_notify text,
IN in5_notify text,
IN in6_notify text,
IN in7_notify text,
IN in8_notify text,
IN in2_reserve_pub BYTEA,
IN in2_wire_ref INT8,
IN in2_credit_val INT8,
IN in2_credit_frac INT4,
IN in2_exchange_account_name VARCHAR,
IN in2_exectution_date INT8,
IN in2_wire_source_h_payto BYTEA, ---h_payto
IN in2_payto_uri VARCHAR,
IN in2_reserve_expiration INT8,
IN in3_reserve_pub BYTEA,
IN in3_wire_ref INT8,
IN in3_credit_val INT8,
IN in3_credit_frac INT4,
IN in3_exchange_account_name VARCHAR,
IN in3_exectution_date INT8,
IN in3_wire_source_h_payto BYTEA, ---h_payto
IN in3_payto_uri VARCHAR,
IN in3_reserve_expiration INT8,
IN in4_reserve_pub BYTEA,
IN in4_wire_ref INT8,
IN in4_credit_val INT8,
IN in4_credit_frac INT4,
IN in4_exchange_account_name VARCHAR,
IN in4_exectution_date INT8,
IN in4_wire_source_h_payto BYTEA, ---h_payto
IN in4_payto_uri VARCHAR,
IN in4_reserve_expiration INT8,
IN in5_reserve_pub BYTEA,
IN in5_wire_ref INT8,
IN in5_credit_val INT8,
IN in5_credit_frac INT4,
IN in5_exchange_account_name VARCHAR,
IN in5_exectution_date INT8,
IN in5_wire_source_h_payto BYTEA, ---h_payto
IN in5_payto_uri VARCHAR,
IN in5_reserve_expiration INT8,
IN in6_reserve_pub BYTEA,
IN in6_wire_ref INT8,
IN in6_credit_val INT8,
IN in6_credit_frac INT4,
IN in6_exchange_account_name VARCHAR,
IN in6_exectution_date INT8,
IN in6_wire_source_h_payto BYTEA, ---h_payto
IN in6_payto_uri VARCHAR,
IN in6_reserve_expiration INT8,
IN in7_reserve_pub BYTEA,
IN in7_wire_ref INT8,
IN in7_credit_val INT8,
IN in7_credit_frac INT4,
IN in7_exchange_account_name VARCHAR,
IN in7_exectution_date INT8,
IN in7_wire_source_h_payto BYTEA, ---h_payto
IN in7_payto_uri VARCHAR,
IN in7_reserve_expiration INT8,
IN in8_reserve_pub BYTEA,
IN in8_wire_ref INT8,
IN in8_credit_val INT8,
IN in8_credit_frac INT4,
IN in8_exchange_account_name VARCHAR,
IN in8_exectution_date INT8,
IN in8_wire_source_h_payto BYTEA, ---h_payto
IN in8_payto_uri VARCHAR,
IN in8_reserve_expiration INT8,
OUT out_reserve_found BOOLEAN,
OUT out_reserve_found2 BOOLEAN,
OUT out_reserve_found3 BOOLEAN,
OUT out_reserve_found4 BOOLEAN,
OUT out_reserve_found5 BOOLEAN,
OUT out_reserve_found6 BOOLEAN,
OUT out_reserve_found7 BOOLEAN,
OUT out_reserve_found8 BOOLEAN,
OUT transaction_duplicate BOOLEAN,
OUT transaction_duplicate2 BOOLEAN,
OUT transaction_duplicate3 BOOLEAN,
OUT transaction_duplicate4 BOOLEAN,
OUT transaction_duplicate5 BOOLEAN,
OUT transaction_duplicate6 BOOLEAN,
OUT transaction_duplicate7 BOOLEAN,
OUT transaction_duplicate8 BOOLEAN,
OUT ruuid INT8,
OUT ruuid2 INT8,
OUT ruuid3 INT8,
OUT ruuid4 INT8,
OUT ruuid5 INT8,
OUT ruuid6 INT8,
OUT ruuid7 INT8,
OUT ruuid8 INT8)
LANGUAGE plpgsql
AS $$
DECLARE
curs_reserve_existed refcursor;
DECLARE
k INT8;
DECLARE
curs_transaction_existed refcursor;
DECLARE
i RECORD;
DECLARE
r RECORD;
BEGIN
--INITIALIZATION
transaction_duplicate=TRUE;
transaction_duplicate2=TRUE;
transaction_duplicate3=TRUE;
transaction_duplicate4=TRUE;
transaction_duplicate5=TRUE;
transaction_duplicate6=TRUE;
transaction_duplicate7=TRUE;
transaction_duplicate8=TRUE;
out_reserve_found = TRUE;
out_reserve_found2 = TRUE;
out_reserve_found3 = TRUE;
out_reserve_found4 = TRUE;
out_reserve_found5 = TRUE;
out_reserve_found6 = TRUE;
out_reserve_found7 = TRUE;
out_reserve_found8 = TRUE;
ruuid=0;
ruuid2=0;
ruuid3=0;
ruuid4=0;
ruuid5=0;
ruuid6=0;
ruuid7=0;
ruuid8=0;
k=0;
--SIMPLE INSERT ON CONFLICT DO NOTHING
INSERT INTO wire_targets
(wire_target_h_payto
,payto_uri)
VALUES
(in_wire_source_h_payto
,in_payto_uri),
(in2_wire_source_h_payto
,in2_payto_uri),
(in3_wire_source_h_payto
,in3_payto_uri),
(in4_wire_source_h_payto
,in4_payto_uri),
(in5_wire_source_h_payto
,in5_payto_uri),
(in6_wire_source_h_payto
,in6_payto_uri),
(in7_wire_source_h_payto
,in7_payto_uri),
(in8_wire_source_h_payto
,in8_payto_uri)
ON CONFLICT DO NOTHING;
OPEN curs_reserve_existed FOR
WITH reserve_changes AS (
INSERT INTO reserves
(reserve_pub
,current_balance_val
,current_balance_frac
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_credit_val
,in_credit_frac
,in_expiration_date
,in_gc_date),
(in2_reserve_pub
,in2_credit_val
,in2_credit_frac
,in_expiration_date
,in_gc_date),
(in3_reserve_pub
,in3_credit_val
,in3_credit_frac
,in_expiration_date
,in_gc_date),
(in4_reserve_pub
,in4_credit_val
,in4_credit_frac
,in_expiration_date
,in_gc_date),
(in5_reserve_pub
,in5_credit_val
,in5_credit_frac
,in_expiration_date
,in_gc_date),
(in6_reserve_pub
,in6_credit_val
,in6_credit_frac
,in_expiration_date
,in_gc_date),
(in7_reserve_pub
,in7_credit_val
,in7_credit_frac
,in_expiration_date
,in_gc_date),
(in8_reserve_pub
,in8_credit_val
,in8_credit_frac
,in_expiration_date
,in_gc_date)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid,reserve_pub)
SELECT * FROM reserve_changes;
WHILE k < 8 LOOP
FETCH FROM curs_reserve_existed INTO i;
IF FOUND
THEN
IF in_reserve_pub = i.reserve_pub
THEN
ruuid = i.reserve_uuid;
IF in_reserve_pub
NOT IN (in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found = FALSE;
END IF;
END IF;
IF in2_reserve_pub = i.reserve_pub
THEN
ruuid2 = i.reserve_uuid;
IF in2_reserve_pub
NOT IN (in_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found2 = FALSE;
END IF;
END IF;
IF in3_reserve_pub = i.reserve_pub
THEN
ruuid3 = i.reserve_uuid;
IF in3_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found3 = FALSE;
END IF;
END IF;
IF in4_reserve_pub = i.reserve_pub
THEN
ruuid4 = i.reserve_uuid;
IF in4_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found4 = FALSE;
END IF;
END IF;
IF in5_reserve_pub = i.reserve_pub
THEN
ruuid5 = i.reserve_uuid;
IF in5_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in6_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found5 = FALSE;
END IF;
END IF;
IF in6_reserve_pub = i.reserve_pub
THEN
ruuid6 = i.reserve_uuid;
IF in6_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in7_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found6 = FALSE;
END IF;
END IF;
IF in7_reserve_pub = i.reserve_pub
THEN
ruuid7 = i.reserve_uuid;
IF in7_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in8_reserve_pub)
THEN
out_reserve_found7 = FALSE;
END IF;
END IF;
IF in8_reserve_pub = i.reserve_pub
THEN
ruuid8 = i.reserve_uuid;
IF in8_reserve_pub
NOT IN (in_reserve_pub
,in2_reserve_pub
,in3_reserve_pub
,in4_reserve_pub
,in5_reserve_pub
,in6_reserve_pub
,in7_reserve_pub)
THEN
out_reserve_found8 = FALSE;
END IF;
END IF;
END IF;
k=k+1;
END LOOP;
CLOSE curs_reserve_existed;
PERFORM pg_notify(in_notify, NULL);
PERFORM pg_notify(in2_notify, NULL);
PERFORM pg_notify(in3_notify, NULL);
PERFORM pg_notify(in4_notify, NULL);
PERFORM pg_notify(in5_notify, NULL);
PERFORM pg_notify(in6_notify, NULL);
PERFORM pg_notify(in7_notify, NULL);
PERFORM pg_notify(in8_notify, NULL);
k=0;
OPEN curs_transaction_existed FOR
WITH reserve_in_changes AS (
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit_val
,credit_frac
,exchange_account_section
,wire_source_h_payto
,execution_date)
VALUES
(in_reserve_pub
,in_wire_ref
,in_credit_val
,in_credit_frac
,in_exchange_account_name
,in_wire_source_h_payto
,in_expiration_date),
(in2_reserve_pub
,in2_wire_ref
,in2_credit_val
,in2_credit_frac
,in2_exchange_account_name
,in2_wire_source_h_payto
,in_expiration_date),
(in3_reserve_pub
,in3_wire_ref
,in3_credit_val
,in3_credit_frac
,in3_exchange_account_name
,in3_wire_source_h_payto
,in_expiration_date),
(in4_reserve_pub
,in4_wire_ref
,in4_credit_val
,in4_credit_frac
,in4_exchange_account_name
,in4_wire_source_h_payto
,in_expiration_date),
(in5_reserve_pub
,in5_wire_ref
,in5_credit_val
,in5_credit_frac
,in5_exchange_account_name
,in5_wire_source_h_payto
,in_expiration_date),
(in6_reserve_pub
,in6_wire_ref
,in6_credit_val
,in6_credit_frac
,in6_exchange_account_name
,in6_wire_source_h_payto
,in_expiration_date),
(in7_reserve_pub
,in7_wire_ref
,in7_credit_val
,in7_credit_frac
,in7_exchange_account_name
,in7_wire_source_h_payto
,in_expiration_date),
(in8_reserve_pub
,in8_wire_ref
,in8_credit_val
,in8_credit_frac
,in8_exchange_account_name
,in8_wire_source_h_payto
,in_expiration_date)
ON CONFLICT DO NOTHING
RETURNING reserve_pub)
SELECT * FROM reserve_in_changes;
WHILE k < 8 LOOP
FETCH FROM curs_transaction_existed INTO r;
IF FOUND
THEN
IF in_reserve_pub = r.reserve_pub
THEN
transaction_duplicate = FALSE;
END IF;
IF in2_reserve_pub = r.reserve_pub
THEN
transaction_duplicate2 = FALSE;
END IF;
IF in3_reserve_pub = r.reserve_pub
THEN
transaction_duplicate3 = FALSE;
END IF;
IF in4_reserve_pub = r.reserve_pub
THEN
transaction_duplicate4 = FALSE;
END IF;
IF in5_reserve_pub = r.reserve_pub
THEN
transaction_duplicate5 = FALSE;
END IF;
IF in6_reserve_pub = r.reserve_pub
THEN
transaction_duplicate6 = FALSE;
END IF;
IF in7_reserve_pub = r.reserve_pub
THEN
transaction_duplicate7 = FALSE;
END IF;
IF in8_reserve_pub = r.reserve_pub
THEN
transaction_duplicate8 = FALSE;
END IF;
END IF;
k=k+1;
END LOOP;
/* IF transaction_duplicate
OR transaction_duplicate2
OR transaction_duplicate3
OR transaction_duplicate4
OR transaction_duplicate5
OR transaction_duplicate6
OR transaction_duplicate7
OR transaction_duplicate8
THEN
CLOSE curs_transaction_existed;
ROLLBACK;
RETURN;
END IF;*/
CLOSE curs_transaction_existed;
RETURN;
END $$;

View File

@ -1,477 +0,0 @@
--
-- 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_batch4_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
IN in_coin_pub2 BYTEA,
IN in_denom_pub_hash2 BYTEA,
IN in_h_age_commitment2 BYTEA,
IN in_denom_sig2 BYTEA,
IN in_coin_pub3 BYTEA,
IN in_denom_pub_hash3 BYTEA,
IN in_h_age_commitment3 BYTEA,
IN in_denom_sig3 BYTEA,
IN in_coin_pub4 BYTEA,
IN in_denom_pub_hash4 BYTEA,
IN in_h_age_commitment4 BYTEA,
IN in_denom_sig4 BYTEA,
OUT existed1 BOOLEAN,
OUT existed2 BOOLEAN,
OUT existed3 BOOLEAN,
OUT existed4 BOOLEAN,
OUT known_coin_id1 INT8,
OUT known_coin_id2 INT8,
OUT known_coin_id3 INT8,
OUT known_coin_id4 INT8,
OUT denom_pub_hash1 BYTEA,
OUT denom_pub_hash2 BYTEA,
OUT denom_pub_hash3 BYTEA,
OUT denom_pub_hash4 BYTEA,
OUT age_commitment_hash1 BYTEA,
OUT age_commitment_hash2 BYTEA,
OUT age_commitment_hash3 BYTEA,
OUT age_commitment_hash4 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin_val, coin_frac
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2,
in_denom_pub_hash3,
in_denom_pub_hash4)
),--dd
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1),
(in_coin_pub2,
in_denom_pub_hash2,
in_h_age_commitment2,
in_denom_sig2),
(in_coin_pub3,
in_denom_pub_hash3,
in_h_age_commitment3,
in_denom_sig3),
(in_coin_pub4,
in_denom_pub_hash4,
in_h_age_commitment4,
in_denom_sig4)
),--ir
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining_val,
remaining_frac
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin_val,
dd.coin_frac
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),--kc
exists AS (
SELECT
CASE
WHEN
ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)--exists
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS existed2,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS known_coin_id2,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS denom_pub_hash2,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
)AS age_commitment_hash2,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS existed3,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS known_coin_id3,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
) AS denom_pub_hash3,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash3
)AS age_commitment_hash3,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS existed4,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS known_coin_id4,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
) AS denom_pub_hash4,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash4
)AS age_commitment_hash4
FROM exists;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
IN in_coin_pub2 BYTEA,
IN in_denom_pub_hash2 BYTEA,
IN in_h_age_commitment2 BYTEA,
IN in_denom_sig2 BYTEA,
OUT existed1 BOOLEAN,
OUT existed2 BOOLEAN,
OUT known_coin_id1 INT8,
OUT known_coin_id2 INT8,
OUT denom_pub_hash1 BYTEA,
OUT denom_pub_hash2 BYTEA,
OUT age_commitment_hash1 BYTEA,
OUT age_commitment_hash2 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin_val, coin_frac
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2)
),--dd
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1),
(in_coin_pub2,
in_denom_pub_hash2,
in_h_age_commitment2,
in_denom_sig2)
),--ir
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining_val,
remaining_frac
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin_val,
dd.coin_frac
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),--kc
exists AS (
SELECT
CASE
WHEN ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)--exists
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1,
(
SELECT exists.existed
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS existed2,
(
SELECT exists.known_coin_id
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS known_coin_id2,
(
SELECT exists.denom_pub_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
) AS denom_pub_hash2,
(
SELECT exists.age_commitment_hash
FROM exists
WHERE exists.denom_pub_hash = in_denom_pub_hash2
)AS age_commitment_hash2
FROM exists;
RETURN;
END $$;
CREATE OR REPLACE FUNCTION exchange_do_batch1_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 BYTEA,
IN in_h_age_commitment1 BYTEA,
IN in_denom_sig1 BYTEA,
OUT existed1 BOOLEAN,
OUT known_coin_id1 INT8,
OUT denom_pub_hash1 BYTEA,
OUT age_commitment_hash1 BYTEA)
LANGUAGE plpgsql
AS $$
BEGIN
WITH dd AS (
SELECT
denominations_serial,
coin_val, coin_frac
FROM denominations
WHERE denom_pub_hash
IN
(in_denom_pub_hash1,
in_denom_pub_hash2)
),--dd
input_rows AS (
VALUES
(in_coin_pub1,
in_denom_pub_hash1,
in_h_age_commitment1,
in_denom_sig1)
),--ir
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining_val,
remaining_frac
)
SELECT
ir.coin_pub,
dd.denominations_serial,
ir.age_commitment_hash,
ir.denom_sig,
dd.coin_val,
dd.coin_frac
FROM input_rows ir
JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
ON CONFLICT DO NOTHING
RETURNING known_coin_id
),--kc
exists AS (
SELECT
CASE
WHEN ins.known_coin_id IS NOT NULL
THEN
FALSE
ELSE
TRUE
END AS existed,
ins.known_coin_id,
dd.denom_pub_hash,
kc.age_commitment_hash
FROM input_rows ir
LEFT JOIN ins
ON ins.coin_pub = ir.coin_pub
LEFT JOIN known_coins kc
ON kc.coin_pub = ir.coin_pub
LEFT JOIN dd
ON dd.denom_pub_hash = ir.denom_pub_hash
)--exists
SELECT
exists.existed AS existed1,
exists.known_coin_id AS known_coin_id1,
exists.denom_pub_hash AS denom_pub_hash1,
exists.age_commitment_hash AS age_commitment_hash1
FROM exists;
RETURN;
END $$;
/*** Experiment using a loop ***/
/*
CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
IN in_coin_pub1 BYTEA,
IN in_denom_pub_hash1 TEXT,
IN in_h_age_commitment1 TEXT,
IN in_denom_sig1 TEXT,
IN in_coin_pub2 BYTEA,
IN in_denom_pub_hash2 TEXT,
IN in_h_age_commitment2 TEXT,
IN in_denom_sig2 TEXT,
OUT existed1 BOOLEAN,
OUT existed2 BOOLEAN,
OUT known_coin_id1 INT8,
OUT known_coin_id2 INT8,
OUT denom_pub_hash1 TEXT,
OUT denom_pub_hash2 TEXT,
OUT age_commitment_hash1 TEXT,
OUT age_commitment_hash2 TEXT)
LANGUAGE plpgsql
AS $$
DECLARE
ins_values RECORD;
BEGIN
FOR i IN 1..2 LOOP
ins_values := (
SELECT
in_coin_pub1 AS coin_pub,
in_denom_pub_hash1 AS denom_pub_hash,
in_h_age_commitment1 AS age_commitment_hash,
in_denom_sig1 AS denom_sig
WHERE i = 1
UNION
SELECT
in_coin_pub2 AS coin_pub,
in_denom_pub_hash2 AS denom_pub_hash,
in_h_age_commitment2 AS age_commitment_hash,
in_denom_sig2 AS denom_sig
WHERE i = 2
);
WITH dd (denominations_serial, coin_val, coin_frac) AS (
SELECT denominations_serial, coin_val, coin_frac
FROM denominations
WHERE denom_pub_hash = ins_values.denom_pub_hash
),
input_rows(coin_pub) AS (
VALUES (ins_values.coin_pub)
),
ins AS (
INSERT INTO known_coins (
coin_pub,
denominations_serial,
age_commitment_hash,
denom_sig,
remaining_val,
remaining_frac
) SELECT
input_rows.coin_pub,
dd.denominations_serial,
ins_values.age_commitment_hash,
ins_values.denom_sig,
coin_val,
coin_frac
FROM dd
CROSS JOIN input_rows
ON CONFLICT DO NOTHING
RETURNING known_coin_id, denom_pub_hash
)
SELECT
CASE i
WHEN 1 THEN
COALESCE(ins.known_coin_id, 0) <> 0 AS existed1,
ins.known_coin_id AS known_coin_id1,
ins.denom_pub_hash AS denom_pub_hash1,
ins.age_commitment_hash AS age_commitment_hash1
WHEN 2 THEN
COALESCE(ins.known_coin_id, 0) <> 0 AS existed2,
ins.known_coin_id AS known_coin_id2,
ins.denom_pub_hash AS denom_pub_hash2,
ins.age_commitment_hash AS age_commitment_hash2
END
FROM ins;
END LOOP;
END;
$$;*/

View File

@ -0,0 +1,116 @@
--
-- 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_batch_reserves_in_insert(
IN in_reserve_pub BYTEA,
IN in_expiration_date INT8,
IN in_gc_date INT8,
IN in_wire_ref INT8,
IN in_credit_val INT8,
IN in_credit_frac INT4,
IN in_exchange_account_name VARCHAR,
IN in_exectution_date INT8,
IN in_wire_source_h_payto BYTEA, ---h_payto
IN in_payto_uri VARCHAR,
IN in_reserve_expiration INT8,
IN in_notify text,
OUT out_reserve_found BOOLEAN,
OUT transaction_duplicate BOOLEAN,
OUT ruuid INT8)
LANGUAGE plpgsql
AS $$
DECLARE
curs refcursor;
DECLARE
i RECORD;
DECLARE
curs_trans refcursor;
BEGIN
ruuid= 0;
out_reserve_found = TRUE;
transaction_duplicate= TRUE;
--SIMPLE INSERT ON CONFLICT DO NOTHING
INSERT INTO wire_targets
(wire_target_h_payto
,payto_uri)
VALUES
(in_wire_source_h_payto
,in_payto_uri)
ON CONFLICT DO NOTHING;
OPEN curs FOR
WITH reserve_changes AS (
INSERT INTO reserves
(reserve_pub
,current_balance_val
,current_balance_frac
,expiration_date
,gc_date)
VALUES
(in_reserve_pub
,in_credit_val
,in_credit_frac
,in_expiration_date
,in_gc_date)
ON CONFLICT DO NOTHING
RETURNING reserve_uuid, reserve_pub)
SELECT * FROM reserve_changes;
FETCH FROM curs INTO i;
IF FOUND
THEN
-- We made a change, so the reserve did not previously exist.
IF in_reserve_pub = i.reserve_pub
THEN
out_reserve_found = FALSE;
ruuid = i.reserve_uuid;
END IF;
END IF;
CLOSE curs;
PERFORM pg_notify(in_notify, NULL);
OPEN curs_trans FOR
WITH reserve_transaction AS(
INSERT INTO reserves_in
(reserve_pub
,wire_reference
,credit_val
,credit_frac
,exchange_account_section
,wire_source_h_payto
,execution_date)
VALUES
(in_reserve_pub
,in_wire_ref
,in_credit_val
,in_credit_frac
,in_exchange_account_name
,in_wire_source_h_payto
,in_expiration_date)
ON CONFLICT DO NOTHING
RETURNING reserve_pub)
SELECT * FROM reserve_transaction;
FETCH FROM curs_trans INTO i;
IF FOUND
THEN
IF i.reserve_pub = in_reserve_pub
THEN
-- HAPPY PATH THERE IS NO DUPLICATE TRANS
transaction_duplicate = FALSE;
END IF;
END IF;
CLOSE curs_trans;
RETURN;
END $$;

View File

@ -123,7 +123,6 @@ THEN
-- Deposit exists, but with differences. Not allowed.
out_balance_ok=FALSE;
out_conflict=TRUE;
out_exchange_timestamp=0;
RETURN;
END IF;

View File

@ -1,59 +0,0 @@
--
-- 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_link_data(
IN in_coin_pub BYTEA
)
RETURNS SETOF record
LANGUAGE plpgsql
AS $$
DECLARE
curs CURSOR
FOR
SELECT
melt_serial_id
FROM refresh_commitments
WHERE old_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
tp.transfer_pub
,denoms.denom_pub
,rrc.ev_sig
,rrc.ewv
,rrc.link_sig
,rrc.freshcoin_index
,rrc.coin_ev
FROM refresh_revealed_coins rrc
JOIN refresh_transfer_keys tp
ON (tp.melt_serial_id=rrc.melt_serial_id)
JOIN denominations denoms
ON (rrc.denominations_serial=denoms.denominations_serial)
WHERE rrc.melt_serial_id =i.melt_serial_id
/* GROUP BY tp.transfer_pub, denoms.denom_pub, rrc.ev_sig,rrc.ewv,rrc.link_sig,rrc.freshcoin_index, rrc.coin_ev*/
ORDER BY tp.transfer_pub,
rrc.freshcoin_index ASC
;
END LOOP;
CLOSE curs;
END $$;

View File

@ -1,69 +0,0 @@
--
-- 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;
DECLARE
curs CURSOR
FOR
SELECT
coin_pub
,deposit_serial_id
,wire_deadline
,shard
FROM deposits_by_ready
WHERE wire_deadline <= in_now
AND shard >=in_start_shard_now
AND shard <=in_end_shard_now
LIMIT 1;
DECLARE
i RECORD;
BEGIN
OPEN curs;
FETCH FROM curs INTO i;
IF NOT FOUND
THEN
RETURN;
END IF;
SELECT
payto_uri
,merchant_pub
INTO
out_payto_uri
,out_merchant_pub
FROM deposits dep
JOIN wire_targets wt
ON (wt.wire_target_h_payto=dep.wire_target_h_payto)
WHERE dep.coin_pub=i.coin_pub
AND dep.deposit_serial_id=i.deposit_serial_id
ORDER BY
i.wire_deadline ASC
,i.shard ASC;
RETURN;
END $$;

View File

@ -1,129 +0,0 @@
--
-- 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,
IN in_notify_s VARCHAR,
IN in_kyc_requirements VARCHAR,
IN in_requirement_row INT8,
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_new_threshold_val
,threshold_frac=in_new_threshold_frac
,status=in_new_status
,kyc_requirement=in_requirement_row
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
,kyc_requirement)
VALUES
(in_h_payto
,in_new_threshold_val
,in_new_threshold_frac
,in_new_status
,in_requirement_row);
END IF;
INSERT INTO exchange.aml_history
(h_payto
,new_threshold_val
,new_threshold_frac
,new_status
,decision_time
,justification
,kyc_requirements
,kyc_req_row
,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_kyc_requirements
,in_requirement_row
,in_decider_pub
,in_decider_sig);
-- wake up taler-exchange-aggregator
IF 0 = in_new_status
THEN
INSERT INTO kyc_alerts
(h_payto
,trigger_type)
VALUES
(in_h_payto,1);
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END IF;
END $$;
COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, INT8, INT4, INT4, INT8, VARCHAR, BYTEA, BYTEA, VARCHAR, VARCHAR, INT8)
IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';

Some files were not shown because too many files have changed in this diff Show More