Compare commits
211 Commits
master-bro
...
master
Author | SHA1 | Date | |
---|---|---|---|
5ee567d1ba | |||
84bde679a7 | |||
e68d9f9b75 | |||
153a078ca5 | |||
1a63275d98 | |||
|
af3c92f9d5 | ||
|
e1439e6401 | ||
|
487f23502f | ||
|
1a3dbf8c98 | ||
|
505170ce1f | ||
|
b219366cdf | ||
08b420dd52 | |||
|
88a69ac7f2 | ||
5d17c9c909 | |||
6f492b2a97 | |||
89a9224c3b | |||
12681dfa1a | |||
37dd5bed20 | |||
|
acbee86745 | ||
|
c3fc8c5e55 | ||
|
76b934b219 | ||
|
be1d8afaec | ||
|
0236caf354 | ||
|
9e61579c8b | ||
|
89c5a3eca9 | ||
|
53157062cb | ||
|
2dab1fac1c | ||
|
5290453e36 | ||
|
03deaeb108 | ||
|
ee6ec1f55d | ||
|
44e0e00595 | ||
|
8952a87b85 | ||
|
8463572bea | ||
|
ade7586c30 | ||
|
10c779bbc6 | ||
|
5121c6b1cf | ||
|
2906ded1a6 | ||
|
136d2b2e70 | ||
|
376de032b5 | ||
|
32c6999a83 | ||
|
eec4dc80ef | ||
|
2c28f7ebd0 | ||
|
07a089f4f1 | ||
|
eb2b4a131b | ||
|
4e9c43954e | ||
|
122c926493 | ||
|
27c9fef5ea | ||
|
090c532b3a | ||
|
677ac4a5c8 | ||
|
cbabddf013 | ||
|
3137d8dc13 | ||
|
36b2cbb47e | ||
|
d4f9417d8c | ||
|
979ec38ec4 | ||
|
e99450e2e2 | ||
|
a30827fcef | ||
|
6eed8917c3 | ||
|
9cce35d270 | ||
|
0c2d5bba55 | ||
|
6af9fd66fb | ||
|
cb87b6f646 | ||
|
d83c2539bc | ||
|
fb70814d46 | ||
|
42258d5778 | ||
|
39f2d441f7 | ||
|
5dfa56727e | ||
f87eda140c | |||
838f6b7f1d | |||
837c53552e | |||
2cca5dff2a | |||
6a3da22546 | |||
a2c70ff0c8 | |||
b15c8e527b | |||
187ae6f8a2 | |||
62da9cca27 | |||
f5080a3b91 | |||
82bcd0d259 | |||
9c66f27034 | |||
777a4c07cf | |||
e3d5672cbd | |||
257f2eb91b | |||
ce71db2c0b | |||
4931e30948 | |||
|
9d5549d6ba | ||
|
74facbead4 | ||
|
269425672c | ||
|
90664b555c | ||
|
890c962817 | ||
|
21c9dae382 | ||
5608a73c00 | |||
b7e20eb71e | |||
7521ff1cf4 | |||
2d1583f96b | |||
6adc223028 | |||
20cd46f63d | |||
|
2c78cb71e6 | ||
262b470878 | |||
|
e2deb89a3d | ||
af1001bc42 | |||
|
70645cbb1b | ||
|
e2185233f6 | ||
|
ce205f93a2 | ||
|
d24423e8f6 | ||
|
442002282d | ||
|
b10d990afd | ||
|
c9d0e4a473 | ||
|
2ad12de668 | ||
|
4eb2c3e78c | ||
|
dd59f3eea6 | ||
|
72ad473fde | ||
|
263ebf00fc | ||
|
b46c03b2c9 | ||
468006c60b | |||
b4128c2c2a | |||
|
7f518fff1a | ||
|
f767a9d12c | ||
|
c6d50abecc | ||
|
8f5dc40217 | ||
|
4a51b9a9a1 | ||
|
7da69142b4 | ||
|
95bd24916e | ||
|
b663c8a3c1 | ||
|
a7f0611a88 | ||
|
3e6a6f0ee6 | ||
|
b43cf6f97f | ||
|
185391f3fc | ||
|
13d90bb1a3 | ||
|
e2fe36a0be | ||
|
95e3087984 | ||
|
9a841f6047 | ||
|
d49a0536ad | ||
|
fe79f6af9c | ||
|
0fe0c414e2 | ||
|
b414183283 | ||
|
2fd87736b4 | ||
|
06e2e8022c | ||
|
aa5e7d2ad5 | ||
|
86e0f2c70d | ||
|
87a78c6f8c | ||
4d2d0473c3 | |||
|
afe3f70d33 | ||
|
437e6ec86a | ||
|
57e2f38bd2 | ||
|
a79e50505b | ||
|
dc40f6c679 | ||
|
3760d43097 | ||
|
6db4bdbe6e | ||
174022907b | |||
|
923ff3126e | ||
|
19132b6716 | ||
|
d0b43b0e6a | ||
|
5c983bd05e | ||
|
c0e6ce7519 | ||
|
6d3efbe900 | ||
|
f079cff4ae | ||
|
64b2bc4558 | ||
|
ab03ba16e9 | ||
|
e66087987f | ||
|
5a18e955eb | ||
|
47b9ef598d | ||
|
4c5394fd4d | ||
|
19da4bd638 | ||
|
c3243aa39f | ||
|
f6877449eb | ||
|
e0687b90f1 | ||
|
ba3b53cd27 | ||
|
a703171f08 | ||
|
f60b09f8ef | ||
|
3898054b10 | ||
|
eab95d0154 | ||
|
915542e69c | ||
|
f8ff9c996f | ||
|
e469e6698e | ||
|
f0567567fe | ||
|
d738287953 | ||
|
d93006c354 | ||
|
09c043c177 | ||
|
4bb96abc97 | ||
|
a1c0c2fafd | ||
|
e8c8aa9efe | ||
|
ff202ef296 | ||
|
99753a5d31 | ||
|
92f16aad51 | ||
|
2aff69e7ec | ||
|
8c5a12302e | ||
|
35d50ba36a | ||
|
0eb6f73176 | ||
|
85e44ceea6 | ||
|
59716ffdc4 | ||
|
d79c23aaab | ||
|
6da3cbedd4 | ||
|
32fac55f7e | ||
|
42bd2dadcf | ||
|
c239ba6f18 | ||
|
7e8e2f4317 | ||
|
f199b45e52 | ||
|
10cf3b3b65 | ||
|
54fa07f5c7 | ||
|
a273b176da | ||
|
c2eee251c2 | ||
|
d53dd753e1 | ||
|
f221db1c03 | ||
|
f8bfc4dc9d | ||
|
d131951fbe | ||
|
31286b66f2 | ||
c1502e507b | |||
|
5b26bd3b83 | ||
|
c782615262 | ||
|
56cdb7e9e6 | ||
|
ebb2601278 | ||
|
c30ee88336 |
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -7,4 +7,4 @@
|
||||
branch = prebuilt
|
||||
[submodule "contrib/gana"]
|
||||
path = contrib/gana
|
||||
url = https://git.gnunet.org/git/gana.git
|
||||
url = https://git.gnunet.org/gana.git
|
||||
|
@ -17,7 +17,7 @@
|
||||
#
|
||||
#
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([taler-exchange],[0.9.1],[taler-bug@gnunet.org])
|
||||
AC_INIT([taler-exchange],[0.9.2],[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 832685b6a942a6ebbec8e1e5b8c33b6b85b0a727
|
||||
Subproject commit bd4e73b2ed06269fdee42eaad21acb5be8be9302
|
@ -46,6 +46,12 @@
|
||||
<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
24
debian/changelog
vendored
@ -1,3 +1,27 @@
|
||||
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.
|
||||
|
2
debian/etc-libtalerexchange/taler/taler.conf
vendored
2
debian/etc-libtalerexchange/taler/taler.conf
vendored
@ -30,6 +30,8 @@
|
||||
# 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]
|
||||
|
||||
|
@ -1,7 +1,18 @@
|
||||
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";
|
||||
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";
|
||||
}
|
||||
}
|
@ -2,13 +2,16 @@ server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
#server_name example.com;
|
||||
server_name localhost;
|
||||
|
||||
access_log /var/log/nginx/exchange.log;
|
||||
error_log /var/log/nginx/exchange.err;
|
||||
|
||||
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 "example.com";
|
||||
proxy_set_header X-Forwarded-Host "localhost";
|
||||
#proxy_set_header X-Forwarded-Proto "https";
|
||||
}
|
||||
}
|
||||
|
@ -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,12 +20,14 @@ BASE_URL =
|
||||
# TERMS_ETAG =
|
||||
# PRIVACY_ETAG =
|
||||
|
||||
SERVE = unix
|
||||
UNIXPATH_MODE = 666
|
||||
|
||||
# Bank accounts used by the exchange should be specified here:
|
||||
[exchange-account-1]
|
||||
|
||||
enable_credit = yes
|
||||
enable_debit = yes
|
||||
enable_credit = no
|
||||
enable_debit = no
|
||||
|
||||
# Account identifier in the form of an RFC-8905 payto:// URI.
|
||||
# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
|
||||
@ -34,4 +36,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.secret.conf
|
||||
@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf
|
||||
|
@ -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.
|
||||
|
1
debian/libtalerexchange.install
vendored
1
debian/libtalerexchange.install
vendored
@ -5,6 +5,5 @@ 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*
|
||||
|
13
debian/taler-exchange-offline.postinst
vendored
13
debian/taler-exchange-offline.postinst
vendored
@ -4,20 +4,21 @@ 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 --system taler-exchange-offline
|
||||
addgroup --quiet taler-exchange-offline
|
||||
fi
|
||||
|
||||
if ! getent passwd taler-exchange-offline >/dev/null; then
|
||||
adduser --quiet --system \
|
||||
adduser --quiet \
|
||||
--disabled-password \
|
||||
--system \
|
||||
--shell /bin/bash \
|
||||
--home /home/taler-exchange-offline \
|
||||
--ingroup taler-exchange-offline \
|
||||
--no-create-home \
|
||||
--home ${TALER_HOME} taler-exchange-offline
|
||||
taler-exchange-offline
|
||||
fi
|
||||
|
||||
;;
|
||||
|
5
debian/taler-exchange.postinst
vendored
5
debian/taler-exchange.postinst
vendored
@ -30,6 +30,7 @@ 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}
|
||||
@ -53,10 +54,10 @@ configure)
|
||||
adduser --quiet ${_AGGRUSERNAME} ${_DBGROUPNAME}
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials.secret.conf >/dev/null 2>&1; then
|
||||
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials-1.secret.conf >/dev/null 2>&1; then
|
||||
dpkg-statoverride --add --update \
|
||||
${_WIREUSERNAME} root 460 \
|
||||
/etc/taler/secrets/exchange-accountcredentials.secret.conf
|
||||
/etc/taler/secrets/exchange-accountcredentials-1.secret.conf
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list /etc/taler/secrets/exchange-db.secret.conf >/dev/null 2>&1; then
|
||||
|
@ -7,7 +7,7 @@ After=postgres.service
|
||||
User=taler-exchange-aggregator
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -15,3 +15,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
@ -6,7 +6,7 @@ PartOf=taler-exchange.target
|
||||
User=taler-exchange-aggregator
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -14,3 +14,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
@ -7,7 +7,7 @@ After=network.target postgres.service
|
||||
User=taler-exchange-closer
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -15,3 +15,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
@ -7,7 +7,7 @@ After=postgres.service
|
||||
User=taler-exchange-expire
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -15,3 +15,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
@ -8,11 +8,19 @@ PartOf=taler-exchange.target
|
||||
[Service]
|
||||
User=taler-exchange-httpd
|
||||
Type=simple
|
||||
# Depending on the configuration, the service suicides and then
|
||||
# needs to be restarted.
|
||||
|
||||
# Depending on the configuration, the service process kills itself and then
|
||||
# needs to be restarted. Thus no significant delay on restarts.
|
||||
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
|
||||
|
@ -7,7 +7,7 @@ PartOf=taler-exchange.target
|
||||
User=taler-exchange-wire
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -15,3 +15,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
@ -7,7 +7,8 @@ PartOf=taler-exchange.target
|
||||
User=taler-exchange-wire
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
RuntimeMaxSec=3600s
|
||||
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
@ -7,7 +7,7 @@ PartOf=taler-exchange.target
|
||||
User=taler-exchange-wire
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=100ms
|
||||
RestartSec=1s
|
||||
ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@ -15,3 +15,4 @@ PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=full
|
||||
Slice=taler-exchange.slice
|
||||
RuntimeMaxSec=3600s
|
||||
|
3
debian/taler-exchange.tmpfiles
vendored
3
debian/taler-exchange.tmpfiles
vendored
@ -1,7 +1,8 @@
|
||||
#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-offline 0700 taler-exchange-offline taler-exchange-offline - -
|
||||
d /var/lib/taler/exchange-secmod-cs 0700 taler-exchange-secmod-cs taler-exchange-secmod - -
|
||||
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 - -
|
||||
|
@ -193,7 +193,7 @@ echo " DONE"
|
||||
|
||||
echo -n "Setting up merchant"
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
echo " DONE"
|
||||
@ -214,7 +214,7 @@ bash
|
||||
# {
|
||||
# amountToSpend: "TESTKUDOS:4",
|
||||
# amountToWithdraw: "TESTKUDOS:10",
|
||||
# bankBaseUrl: $BANK_URL,
|
||||
# bankAccessApiBaseUrl: $BANK_URL,
|
||||
# exchangeBaseUrl: $EXCHANGE_URL,
|
||||
# merchantBaseUrl: $MERCHANT_URL,
|
||||
# }' \
|
||||
|
@ -141,6 +141,7 @@ 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
|
||||
|
@ -398,7 +398,7 @@ echo " DONE"
|
||||
|
||||
echo -n "Setting up merchant"
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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",
|
||||
bankBaseUrl: $BANK_URL,
|
||||
bankAccessApiBaseUrl: $BANK_URL,
|
||||
exchangeBaseUrl: $EXCHANGE_URL,
|
||||
merchantBaseUrl: $MERCHANT_URL,
|
||||
}' \
|
||||
|
@ -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"}, "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
|
||||
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
|
||||
|
||||
|
||||
# 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",
|
||||
bankBaseUrl: $BANK_URL,
|
||||
bankAccessApiBaseUrl: $BANK_URL,
|
||||
exchangeBaseUrl: $EXCHANGE_URL,
|
||||
}' \
|
||||
--arg BANK_URL "$BANK_URL/demobanks/default/access-api/" \
|
||||
|
@ -271,7 +271,9 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size)
|
||||
{
|
||||
struct TALER_AUDITORDB_DepositConfirmation dc;
|
||||
struct TALER_AUDITORDB_DepositConfirmation dc = {
|
||||
.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS
|
||||
};
|
||||
struct TALER_AUDITORDB_ExchangeSigningKey es;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
|
||||
@ -282,8 +284,10 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
|
||||
&dc.h_wire),
|
||||
GNUNET_JSON_spec_timestamp ("exchange_timestamp",
|
||||
&dc.exchange_timestamp),
|
||||
GNUNET_JSON_spec_timestamp ("refund_deadline",
|
||||
&dc.refund_deadline),
|
||||
GNUNET_JSON_spec_mark_optional (
|
||||
GNUNET_JSON_spec_timestamp ("refund_deadline",
|
||||
&dc.refund_deadline),
|
||||
NULL),
|
||||
GNUNET_JSON_spec_timestamp ("wire_deadline",
|
||||
&dc.wire_deadline),
|
||||
TALER_JSON_spec_amount ("amount_without_fee",
|
||||
|
@ -885,8 +885,9 @@ handle_purse_decision (
|
||||
report_row_inconsistency ("purse-request",
|
||||
rowid,
|
||||
"purse fee higher than balance");
|
||||
TALER_amount_set_zero (TALER_ARL_currency,
|
||||
&balance_without_purse_fee);
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TALER_ARL_currency,
|
||||
&balance_without_purse_fee));
|
||||
}
|
||||
|
||||
if (refunded)
|
||||
@ -1021,8 +1022,9 @@ verify_purse_balance (void *cls,
|
||||
report_row_inconsistency ("purse",
|
||||
0,
|
||||
"purse fee higher than balance");
|
||||
TALER_amount_set_zero (TALER_ARL_currency,
|
||||
&balance_without_purse_fee);
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TALER_ARL_currency,
|
||||
&balance_without_purse_fee));
|
||||
}
|
||||
|
||||
if (0 != TALER_amount_cmp (&ps->exchange_balance,
|
||||
|
@ -1081,8 +1081,9 @@ handle_reserve_closed (
|
||||
}
|
||||
if (NULL == payto_uri)
|
||||
{
|
||||
if (0 != strcmp (rs->sender_account,
|
||||
receiver_account))
|
||||
if ( (NULL == rs->sender_account) ||
|
||||
(0 != strcmp (rs->sender_account,
|
||||
receiver_account)) )
|
||||
{
|
||||
report_row_inconsistency ("reserves_close",
|
||||
rowid,
|
||||
@ -1110,8 +1111,8 @@ handle_reserve_closed (
|
||||
rowid,
|
||||
"target account not verified, auditor does not know reserve");
|
||||
}
|
||||
if (0 != strcmp (rs->sender_account,
|
||||
receiver_account))
|
||||
else if (0 != strcmp (rs->sender_account,
|
||||
receiver_account))
|
||||
{
|
||||
report_row_inconsistency ("reserves_close",
|
||||
rowid,
|
||||
|
@ -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
|
||||
|
@ -248,12 +248,15 @@ 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",
|
||||
@ -263,6 +266,7 @@ 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",
|
||||
@ -270,6 +274,8 @@ 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
@ -28,7 +28,7 @@ DB = postgres
|
||||
# exchange (or the twister) is actually listening.
|
||||
base_url = "http://localhost:8081/"
|
||||
|
||||
WIREWATCH_IDLE_SLEEP_INTERVAL = 1500 ms
|
||||
WIREWATCH_IDLE_SLEEP_INTERVAL = 500 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
|
||||
USER_PAYTO_URI = payto://x-taler-bank/localhost:8082/42?receiver-name=user42
|
||||
|
||||
[exchange-account-2]
|
||||
# What is the payto://-URL of the exchange (to generate wire response)
|
||||
PAYTO_URI = "payto://x-taler-bank/localhost:8082/Exchange"
|
||||
PAYTO_URI = "payto://x-taler-bank/localhost:8082/Exchange?receiver-name=Exchange"
|
||||
enable_debit = YES
|
||||
enable_credit = YES
|
||||
|
||||
|
@ -689,6 +689,8 @@ 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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2014-2020 Taler Systems SA
|
||||
(C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as
|
||||
|
@ -15,8 +15,7 @@ endif
|
||||
bin_PROGRAMS = \
|
||||
taler-auditor-offline \
|
||||
taler-exchange-offline \
|
||||
taler-exchange-dbinit \
|
||||
taler-crypto-worker
|
||||
taler-exchange-dbinit
|
||||
|
||||
taler_exchange_offline_SOURCES = \
|
||||
taler-exchange-offline.c
|
||||
@ -60,19 +59,6 @@ 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
|
||||
|
||||
|
@ -1,459 +0,0 @@
|
||||
/*
|
||||
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;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2020, 2021, 2022 Taler Systems SA
|
||||
Copyright (C) 2020-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
@ -112,6 +112,16 @@
|
||||
*/
|
||||
#define OP_DRAIN_PROFITS "exchange-drain-profits-0"
|
||||
|
||||
/**
|
||||
* Setup AML staff.
|
||||
*/
|
||||
#define OP_UPDATE_AML_STAFF "exchange-add-aml-staff-0"
|
||||
|
||||
/**
|
||||
* Setup partner exchange for wad transfers.
|
||||
*/
|
||||
#define OP_ADD_PARTNER "exchange-add-partner-0"
|
||||
|
||||
/**
|
||||
* Our private key, initialized in #load_offline_key().
|
||||
*/
|
||||
@ -498,6 +508,62 @@ struct UploadExtensionsRequest
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Data structure for AML staff requests.
|
||||
*/
|
||||
struct AmlStaffRequest
|
||||
{
|
||||
|
||||
/**
|
||||
* Kept in a DLL.
|
||||
*/
|
||||
struct AmlStaffRequest *next;
|
||||
|
||||
/**
|
||||
* Kept in a DLL.
|
||||
*/
|
||||
struct AmlStaffRequest *prev;
|
||||
|
||||
/**
|
||||
* Operation handle.
|
||||
*/
|
||||
struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *h;
|
||||
|
||||
/**
|
||||
* Array index of the associated command.
|
||||
*/
|
||||
size_t idx;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Data structure for partner add requests.
|
||||
*/
|
||||
struct PartnerAddRequest
|
||||
{
|
||||
|
||||
/**
|
||||
* Kept in a DLL.
|
||||
*/
|
||||
struct PartnerAddRequest *next;
|
||||
|
||||
/**
|
||||
* Kept in a DLL.
|
||||
*/
|
||||
struct PartnerAddRequest *prev;
|
||||
|
||||
/**
|
||||
* Operation handle.
|
||||
*/
|
||||
struct TALER_EXCHANGE_ManagementAddPartner *h;
|
||||
|
||||
/**
|
||||
* Array index of the associated command.
|
||||
*/
|
||||
size_t idx;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Next work item to perform.
|
||||
*/
|
||||
@ -508,6 +574,27 @@ static struct GNUNET_SCHEDULER_Task *nxt;
|
||||
*/
|
||||
static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh;
|
||||
|
||||
|
||||
/**
|
||||
* Active AML staff change requests.
|
||||
*/
|
||||
static struct AmlStaffRequest *asr_head;
|
||||
|
||||
/**
|
||||
* Active AML staff change requests.
|
||||
*/
|
||||
static struct AmlStaffRequest *asr_tail;
|
||||
|
||||
/**
|
||||
* Active partner add requests.
|
||||
*/
|
||||
static struct PartnerAddRequest *par_head;
|
||||
|
||||
/**
|
||||
* Active partner add requests.
|
||||
*/
|
||||
static struct PartnerAddRequest *par_tail;
|
||||
|
||||
/**
|
||||
* Active denomiantion revocation requests.
|
||||
*/
|
||||
@ -629,6 +716,36 @@ do_shutdown (void *cls)
|
||||
{
|
||||
(void) cls;
|
||||
|
||||
{
|
||||
struct AmlStaffRequest *asr;
|
||||
|
||||
while (NULL != (asr = asr_head))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Aborting incomplete AML staff update #%u\n",
|
||||
(unsigned int) asr->idx);
|
||||
TALER_EXCHANGE_management_update_aml_officer_cancel (asr->h);
|
||||
GNUNET_CONTAINER_DLL_remove (asr_head,
|
||||
asr_tail,
|
||||
asr);
|
||||
GNUNET_free (asr);
|
||||
}
|
||||
}
|
||||
{
|
||||
struct PartnerAddRequest *par;
|
||||
|
||||
while (NULL != (par = par_head))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Aborting incomplete partner add request #%u\n",
|
||||
(unsigned int) par->idx);
|
||||
TALER_EXCHANGE_management_add_partner_cancel (par->h);
|
||||
GNUNET_CONTAINER_DLL_remove (par_head,
|
||||
par_tail,
|
||||
par);
|
||||
GNUNET_free (par);
|
||||
}
|
||||
}
|
||||
{
|
||||
struct DenomRevocationRequest *drr;
|
||||
|
||||
@ -842,6 +959,8 @@ static void
|
||||
test_shutdown (void)
|
||||
{
|
||||
if ( (NULL == drr_head) &&
|
||||
(NULL == par_head) &&
|
||||
(NULL == asr_head) &&
|
||||
(NULL == srr_head) &&
|
||||
(NULL == aar_head) &&
|
||||
(NULL == adr_head) &&
|
||||
@ -2214,6 +2333,221 @@ upload_extensions (const char *exchange_url,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called with information about the add partner operation.
|
||||
*
|
||||
* @param cls closure with a `struct PartnerAddRequest`
|
||||
* @param hr HTTP response data
|
||||
*/
|
||||
static void
|
||||
add_partner_cb (
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr)
|
||||
{
|
||||
struct PartnerAddRequest *par = cls;
|
||||
|
||||
if (MHD_HTTP_NO_CONTENT != hr->http_status)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Upload failed for command %u with status %u: %s (%s)\n",
|
||||
(unsigned int) par->idx,
|
||||
hr->http_status,
|
||||
TALER_ErrorCode_get_hint (hr->ec),
|
||||
hr->hint);
|
||||
global_ret = EXIT_FAILURE;
|
||||
}
|
||||
GNUNET_CONTAINER_DLL_remove (par_head,
|
||||
par_tail,
|
||||
par);
|
||||
GNUNET_free (par);
|
||||
test_shutdown ();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add partner action.
|
||||
*
|
||||
* @param exchange_url base URL of the exchange
|
||||
* @param idx index of the operation we are performing (for logging)
|
||||
* @param value arguments for add partner
|
||||
*/
|
||||
static void
|
||||
add_partner (const char *exchange_url,
|
||||
size_t idx,
|
||||
const json_t *value)
|
||||
{
|
||||
struct TALER_MasterPublicKeyP partner_pub;
|
||||
struct GNUNET_TIME_Timestamp start_date;
|
||||
struct GNUNET_TIME_Timestamp end_date;
|
||||
struct GNUNET_TIME_Relative wad_frequency;
|
||||
struct TALER_Amount wad_fee;
|
||||
const char *partner_base_url;
|
||||
struct TALER_MasterSignatureP master_sig;
|
||||
struct PartnerAddRequest *par;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("partner_pub",
|
||||
&partner_pub),
|
||||
TALER_JSON_spec_amount ("wad_fee",
|
||||
currency,
|
||||
&wad_fee),
|
||||
GNUNET_JSON_spec_relative_time ("wad_frequency",
|
||||
&wad_frequency),
|
||||
GNUNET_JSON_spec_timestamp ("start_date",
|
||||
&start_date),
|
||||
GNUNET_JSON_spec_timestamp ("end_date",
|
||||
&end_date),
|
||||
GNUNET_JSON_spec_string ("partner_base_url",
|
||||
&partner_base_url),
|
||||
GNUNET_JSON_spec_fixed_auto ("master_sig",
|
||||
&master_sig),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
const char *err_name;
|
||||
unsigned int err_line;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (value,
|
||||
spec,
|
||||
&err_name,
|
||||
&err_line))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Invalid input to add partner: %s#%u at %u (skipping)\n",
|
||||
err_name,
|
||||
err_line,
|
||||
(unsigned int) idx);
|
||||
json_dumpf (value,
|
||||
stderr,
|
||||
JSON_INDENT (2));
|
||||
global_ret = EXIT_FAILURE;
|
||||
test_shutdown ();
|
||||
return;
|
||||
}
|
||||
par = GNUNET_new (struct PartnerAddRequest);
|
||||
par->idx = idx;
|
||||
par->h =
|
||||
TALER_EXCHANGE_management_add_partner (ctx,
|
||||
exchange_url,
|
||||
&partner_pub,
|
||||
start_date,
|
||||
end_date,
|
||||
wad_frequency,
|
||||
&wad_fee,
|
||||
partner_base_url,
|
||||
&master_sig,
|
||||
&add_partner_cb,
|
||||
par);
|
||||
GNUNET_CONTAINER_DLL_insert (par_head,
|
||||
par_tail,
|
||||
par);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called with information about the AML officer update operation.
|
||||
*
|
||||
* @param cls closure with a `struct AmlStaffRequest`
|
||||
* @param hr HTTP response data
|
||||
*/
|
||||
static void
|
||||
update_aml_officer_cb (
|
||||
void *cls,
|
||||
const struct TALER_EXCHANGE_HttpResponse *hr)
|
||||
{
|
||||
struct AmlStaffRequest *asr = cls;
|
||||
|
||||
if (MHD_HTTP_NO_CONTENT != hr->http_status)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Upload failed for command %u with status %u: %s (%s)\n",
|
||||
(unsigned int) asr->idx,
|
||||
hr->http_status,
|
||||
TALER_ErrorCode_get_hint (hr->ec),
|
||||
hr->hint);
|
||||
global_ret = EXIT_FAILURE;
|
||||
}
|
||||
GNUNET_CONTAINER_DLL_remove (asr_head,
|
||||
asr_tail,
|
||||
asr);
|
||||
GNUNET_free (asr);
|
||||
test_shutdown ();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload AML staff action.
|
||||
*
|
||||
* @param exchange_url base URL of the exchange
|
||||
* @param idx index of the operation we are performing (for logging)
|
||||
* @param value arguments for AML staff change
|
||||
*/
|
||||
static void
|
||||
update_aml_staff (const char *exchange_url,
|
||||
size_t idx,
|
||||
const json_t *value)
|
||||
{
|
||||
struct TALER_AmlOfficerPublicKeyP officer_pub;
|
||||
const char *officer_name;
|
||||
struct GNUNET_TIME_Timestamp change_date;
|
||||
bool is_active;
|
||||
bool read_only;
|
||||
struct TALER_MasterSignatureP master_sig;
|
||||
struct AmlStaffRequest *asr;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("officer_pub",
|
||||
&officer_pub),
|
||||
GNUNET_JSON_spec_timestamp ("change_date",
|
||||
&change_date),
|
||||
GNUNET_JSON_spec_bool ("is_active",
|
||||
&is_active),
|
||||
GNUNET_JSON_spec_bool ("read_only",
|
||||
&read_only),
|
||||
GNUNET_JSON_spec_string ("officer_name",
|
||||
&officer_name),
|
||||
GNUNET_JSON_spec_fixed_auto ("master_sig",
|
||||
&master_sig),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
const char *err_name;
|
||||
unsigned int err_line;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (value,
|
||||
spec,
|
||||
&err_name,
|
||||
&err_line))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Invalid input to AML staff update: %s#%u at %u (skipping)\n",
|
||||
err_name,
|
||||
err_line,
|
||||
(unsigned int) idx);
|
||||
json_dumpf (value,
|
||||
stderr,
|
||||
JSON_INDENT (2));
|
||||
global_ret = EXIT_FAILURE;
|
||||
test_shutdown ();
|
||||
return;
|
||||
}
|
||||
asr = GNUNET_new (struct AmlStaffRequest);
|
||||
asr->idx = idx;
|
||||
asr->h =
|
||||
TALER_EXCHANGE_management_update_aml_officer (ctx,
|
||||
exchange_url,
|
||||
&officer_pub,
|
||||
officer_name,
|
||||
change_date,
|
||||
is_active,
|
||||
read_only,
|
||||
&master_sig,
|
||||
&update_aml_officer_cb,
|
||||
asr);
|
||||
GNUNET_CONTAINER_DLL_insert (asr_head,
|
||||
asr_tail,
|
||||
asr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform uploads based on the JSON in #out.
|
||||
*
|
||||
@ -2267,6 +2601,14 @@ trigger_upload (const char *exchange_url)
|
||||
.key = OP_EXTENSIONS,
|
||||
.cb = &upload_extensions
|
||||
},
|
||||
{
|
||||
.key = OP_UPDATE_AML_STAFF,
|
||||
.cb = &update_aml_staff
|
||||
},
|
||||
{
|
||||
.key = OP_ADD_PARTNER,
|
||||
.cb = &add_partner
|
||||
},
|
||||
/* array termination */
|
||||
{
|
||||
.key = NULL
|
||||
@ -3040,6 +3382,261 @@ do_drain (char *const *args)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add partner.
|
||||
*
|
||||
* @param args the array of command-line arguments to process next;
|
||||
* args[0] must be the partner's master public key, args[1] the partner's
|
||||
* API base URL, args[2] the wad fee, args[3] the wad frequency, and
|
||||
* args[4] the year (including possibly 'now')
|
||||
*/
|
||||
static void
|
||||
do_add_partner (char *const *args)
|
||||
{
|
||||
struct TALER_MasterPublicKeyP partner_pub;
|
||||
struct GNUNET_TIME_Timestamp start_date;
|
||||
struct GNUNET_TIME_Timestamp end_date;
|
||||
struct GNUNET_TIME_Relative wad_frequency;
|
||||
struct TALER_Amount wad_fee;
|
||||
const char *partner_base_url;
|
||||
struct TALER_MasterSignatureP master_sig;
|
||||
char dummy;
|
||||
unsigned int year;
|
||||
|
||||
if (NULL != in)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Downloaded data was not consumed, not adding partner\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_FAILURE;
|
||||
return;
|
||||
}
|
||||
if ( (NULL == args[0]) ||
|
||||
(GNUNET_OK !=
|
||||
GNUNET_STRINGS_string_to_data (args[0],
|
||||
strlen (args[0]),
|
||||
&partner_pub,
|
||||
sizeof (partner_pub))) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"You must specify the partner master public key as first argument for this subcommand\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
if ( (NULL == args[1]) ||
|
||||
(0 != strncmp ("http",
|
||||
args[1],
|
||||
strlen ("http"))) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"You must specify the partner's base URL as the 2nd argument to this subcommand\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
partner_base_url = args[1];
|
||||
if ( (NULL == args[2]) ||
|
||||
(GNUNET_OK !=
|
||||
TALER_string_to_amount (args[2],
|
||||
&wad_fee)) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Invalid amount `%s' specified for wad fee of partner\n",
|
||||
args[2]);
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
if ( (NULL == args[3]) ||
|
||||
(GNUNET_OK !=
|
||||
GNUNET_STRINGS_fancy_time_to_relative (args[3],
|
||||
&wad_frequency)) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Invalid wad frequency `%s' specified for add partner\n",
|
||||
args[3]);
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
if ( (NULL == args[4]) ||
|
||||
( (1 != sscanf (args[4],
|
||||
"%u%c",
|
||||
&year,
|
||||
&dummy)) &&
|
||||
(0 != strcasecmp ("now",
|
||||
args[4])) ) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Invalid year `%s' specified for add partner\n",
|
||||
args[4]);
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
if (0 == strcasecmp ("now",
|
||||
args[4]))
|
||||
year = GNUNET_TIME_get_current_year ();
|
||||
start_date = GNUNET_TIME_absolute_to_timestamp (
|
||||
GNUNET_TIME_year_to_time (year));
|
||||
end_date = GNUNET_TIME_absolute_to_timestamp (
|
||||
GNUNET_TIME_year_to_time (year + 1));
|
||||
|
||||
if (GNUNET_OK !=
|
||||
load_offline_key (GNUNET_NO))
|
||||
return;
|
||||
TALER_exchange_offline_partner_details_sign (&partner_pub,
|
||||
start_date,
|
||||
end_date,
|
||||
wad_frequency,
|
||||
&wad_fee,
|
||||
partner_base_url,
|
||||
&master_priv,
|
||||
&master_sig);
|
||||
output_operation (OP_ADD_PARTNER,
|
||||
GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_string ("partner_base_url",
|
||||
partner_base_url),
|
||||
GNUNET_JSON_pack_time_rel ("wad_frequency",
|
||||
wad_frequency),
|
||||
GNUNET_JSON_pack_timestamp ("start_date",
|
||||
start_date),
|
||||
GNUNET_JSON_pack_timestamp ("end_date",
|
||||
end_date),
|
||||
GNUNET_JSON_pack_data_auto ("partner_pub",
|
||||
&partner_pub),
|
||||
GNUNET_JSON_pack_data_auto ("master_sig",
|
||||
&master_sig)));
|
||||
next (args + 5);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable or disable AML staff.
|
||||
*
|
||||
* @param is_active true to enable, false to disable
|
||||
* @param args the array of command-line arguments to process next; args[0] must be the AML staff's public key, args[1] the AML staff's legal name, and if @a is_active then args[2] rw (read write) or ro (read only)
|
||||
*/
|
||||
static void
|
||||
do_set_aml_staff (bool is_active,
|
||||
char *const *args)
|
||||
{
|
||||
struct TALER_AmlOfficerPublicKeyP officer_pub;
|
||||
const char *officer_name;
|
||||
bool read_only;
|
||||
struct TALER_MasterSignatureP master_sig;
|
||||
struct GNUNET_TIME_Timestamp now
|
||||
= GNUNET_TIME_timestamp_get ();
|
||||
|
||||
if (NULL != in)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Downloaded data was not consumed, not updating AML staff status\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_FAILURE;
|
||||
return;
|
||||
}
|
||||
if ( (NULL == args[0]) ||
|
||||
(GNUNET_OK !=
|
||||
GNUNET_STRINGS_string_to_data (args[0],
|
||||
strlen (args[0]),
|
||||
&officer_pub,
|
||||
sizeof (officer_pub))) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"You must specify the AML officer's public key as first argument for this subcommand\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
if (NULL == args[1])
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"You must specify the officer's legal name as the 2nd argument to this subcommand\n");
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
officer_name = args[1];
|
||||
if (is_active)
|
||||
{
|
||||
if ( (NULL == args[2]) ||
|
||||
( (0 != strcmp (args[2],
|
||||
"ro")) &&
|
||||
(0 != strcmp (args[2],
|
||||
"rw")) ) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"You must specify 'ro' or 'rw' (and not `%s') for the access level\n",
|
||||
args[2]);
|
||||
test_shutdown ();
|
||||
global_ret = EXIT_INVALIDARGUMENT;
|
||||
return;
|
||||
}
|
||||
read_only = (0 == strcmp (args[2],
|
||||
"ro"));
|
||||
}
|
||||
else
|
||||
{
|
||||
read_only = true;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
load_offline_key (GNUNET_NO))
|
||||
return;
|
||||
TALER_exchange_offline_aml_officer_status_sign (&officer_pub,
|
||||
officer_name,
|
||||
now,
|
||||
is_active,
|
||||
read_only,
|
||||
&master_priv,
|
||||
&master_sig);
|
||||
output_operation (OP_UPDATE_AML_STAFF,
|
||||
GNUNET_JSON_PACK (
|
||||
GNUNET_JSON_pack_string ("officer_name",
|
||||
officer_name),
|
||||
GNUNET_JSON_pack_timestamp ("change_date",
|
||||
now),
|
||||
GNUNET_JSON_pack_bool ("is_active",
|
||||
is_active),
|
||||
GNUNET_JSON_pack_bool ("read_only",
|
||||
read_only),
|
||||
GNUNET_JSON_pack_data_auto ("officer_pub",
|
||||
&officer_pub),
|
||||
GNUNET_JSON_pack_data_auto ("master_sig",
|
||||
&master_sig)));
|
||||
next (args + (is_active ? 3 : 2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disable AML staff.
|
||||
*
|
||||
* @param args the array of command-line arguments to process next;
|
||||
* args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only)
|
||||
*/
|
||||
static void
|
||||
disable_aml_staff (char *const *args)
|
||||
{
|
||||
do_set_aml_staff (false,
|
||||
args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable AML staff.
|
||||
*
|
||||
* @param args the array of command-line arguments to process next;
|
||||
* args[0] must be the AML staff's public key, args[1] the AML staff's legal name, args[2] rw (read write) or ro (read only)
|
||||
*/
|
||||
static void
|
||||
enable_aml_staff (char *const *args)
|
||||
{
|
||||
do_set_aml_staff (true,
|
||||
args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called with information about future keys. Dumps the JSON output
|
||||
* (on success), either into an internal buffer or to stdout (depending on
|
||||
@ -4475,6 +5072,24 @@ work (void *cls)
|
||||
"drain profits from exchange escrow account to regular exchange operator account (amount, debit account configuration section and credit account payto://-URI must be given as arguments)",
|
||||
.cb = &do_drain
|
||||
},
|
||||
{
|
||||
.name = "add-partner",
|
||||
.help =
|
||||
"add partner exchange for P2P wad transfers (partner master public key, partner base URL, wad fee, wad frequency and validity year must be given as arguments)",
|
||||
.cb = &do_add_partner
|
||||
},
|
||||
{
|
||||
.name = "aml-enable",
|
||||
.help =
|
||||
"enable AML staff member (staff member public key, legal name and rw (read write) or ro (read only) must be given as arguments)",
|
||||
.cb = &enable_aml_staff
|
||||
},
|
||||
{
|
||||
.name = "aml-disable",
|
||||
.help =
|
||||
"disable AML staff member (staff member public key and legal name must be given as arguments)",
|
||||
.cb = &disable_aml_staff
|
||||
},
|
||||
{
|
||||
.name = "upload",
|
||||
.help =
|
||||
|
@ -123,9 +123,15 @@ 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 \
|
||||
@ -139,12 +145,14 @@ taler_exchange_httpd_SOURCES = \
|
||||
taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
|
||||
taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
|
||||
taler-exchange-httpd_management.h \
|
||||
taler-exchange-httpd_management_aml-officers.c \
|
||||
taler-exchange-httpd_management_auditors.c \
|
||||
taler-exchange-httpd_management_auditors_AP_disable.c \
|
||||
taler-exchange-httpd_management_denominations_HDP_revoke.c \
|
||||
taler-exchange-httpd_management_drain.c \
|
||||
taler-exchange-httpd_management_extensions.c \
|
||||
taler-exchange-httpd_management_global_fees.c \
|
||||
taler-exchange-httpd_management_partners.c \
|
||||
taler-exchange-httpd_management_post_keys.c \
|
||||
taler-exchange-httpd_management_signkey_EP_revoke.c \
|
||||
taler-exchange-httpd_management_wire_enable.c \
|
||||
|
@ -6,6 +6,10 @@
|
||||
# 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.
|
||||
@ -15,7 +19,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 = 10000000
|
||||
# MAX_REQUESTS = 100000
|
||||
|
||||
# How to access our database
|
||||
DB = postgres
|
||||
|
@ -148,6 +148,12 @@ 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
|
||||
@ -294,11 +300,20 @@ parse_aggregator_config (void)
|
||||
"taler",
|
||||
"CURRENCY_ROUND_UNIT",
|
||||
¤cy_round_unit)) ||
|
||||
( (0 != currency_round_unit.fraction) &&
|
||||
(0 != currency_round_unit.value) ) )
|
||||
(TALER_amount_is_zero (¤cy_round_unit)) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Need non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
|
||||
"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");
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
@ -481,16 +496,22 @@ return_relevant_amounts (void *cls,
|
||||
static bool
|
||||
kyc_satisfied (struct AggregationUnit *au_active)
|
||||
{
|
||||
const char *requirement;
|
||||
char *requirement;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
requirement = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = 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);
|
||||
(void *) au_active,
|
||||
&requirement);
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
return false;
|
||||
}
|
||||
if (NULL == requirement)
|
||||
return true;
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
@ -514,6 +535,114 @@ 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;
|
||||
}
|
||||
|
||||
@ -643,7 +772,8 @@ do_aggregate (struct AggregationUnit *au)
|
||||
TALER_amount_round_down (&au->final_amount,
|
||||
¤cy_round_unit)) ||
|
||||
(TALER_amount_is_zero (&au->final_amount)) ||
|
||||
(! kyc_satisfied (au)) )
|
||||
(! kyc_satisfied (au)) ||
|
||||
(! aml_satisfied (au)) )
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Not ready for wire transfer (%d/%s)\n",
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
@ -30,9 +30,11 @@
|
||||
#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"
|
||||
@ -78,6 +80,11 @@
|
||||
*/
|
||||
#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?
|
||||
*/
|
||||
@ -131,6 +138,11 @@ 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)
|
||||
*/
|
||||
@ -141,6 +153,13 @@ 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.
|
||||
*/
|
||||
@ -322,6 +341,227 @@ 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.
|
||||
*
|
||||
@ -890,6 +1130,8 @@ handle_post_management (struct TEH_RequestContext *rc,
|
||||
&exchange_pub,
|
||||
root);
|
||||
}
|
||||
/* FIXME-STYLE: all of the following can likely be nicely combined
|
||||
into an array-based dispatcher to deduplicate the logic... */
|
||||
if (0 == strcmp (args[0],
|
||||
"keys"))
|
||||
{
|
||||
@ -967,6 +1209,30 @@ handle_post_management (struct TEH_RequestContext *rc,
|
||||
return TEH_handler_management_post_drain (rc->connection,
|
||||
root);
|
||||
}
|
||||
if (0 == strcmp (args[0],
|
||||
"aml-officers"))
|
||||
{
|
||||
if (NULL != args[1])
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return r404 (rc->connection,
|
||||
"/management/aml-officers/*");
|
||||
}
|
||||
return TEH_handler_management_aml_officers (rc->connection,
|
||||
root);
|
||||
}
|
||||
if (0 == strcmp (args[0],
|
||||
"partners"))
|
||||
{
|
||||
if (NULL != args[1])
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return r404 (rc->connection,
|
||||
"/management/partners/*");
|
||||
}
|
||||
return TEH_handler_management_partners (rc->connection,
|
||||
root);
|
||||
}
|
||||
GNUNET_break_op (0);
|
||||
return r404 (rc->connection,
|
||||
"/management/*");
|
||||
@ -1111,6 +1377,12 @@ 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",
|
||||
@ -1289,6 +1561,22 @@ handle_mhd_request (void *cls,
|
||||
.nargs = 4,
|
||||
.nargs_is_upper_bound = true
|
||||
},
|
||||
/* AML endpoints */
|
||||
{
|
||||
.url = "aml",
|
||||
.method = MHD_HTTP_METHOD_GET,
|
||||
.handler.get = &handle_get_aml,
|
||||
.nargs = 4,
|
||||
.nargs_is_upper_bound = true
|
||||
},
|
||||
{
|
||||
.url = "aml",
|
||||
.method = MHD_HTTP_METHOD_POST,
|
||||
.handler.post = &handle_post_aml,
|
||||
.nargs = 2
|
||||
},
|
||||
|
||||
|
||||
/* mark end of list */
|
||||
{
|
||||
.url = NULL
|
||||
@ -1351,7 +1639,10 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1588,6 +1879,23 @@ 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",
|
||||
@ -1636,17 +1944,46 @@ 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));
|
||||
|
||||
if (NULL ==
|
||||
(TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
|
||||
{
|
||||
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)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Failed to initialize DB subsystem\n");
|
||||
"Failed to initialize DB subsystem. Giving up.\n");
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
return GNUNET_OK;
|
||||
|
@ -82,6 +82,11 @@ 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.
|
||||
*/
|
||||
@ -92,6 +97,13 @@ 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.
|
||||
*/
|
||||
@ -213,7 +225,7 @@ struct TEH_RequestHandler
|
||||
*
|
||||
* @param rc context for the request
|
||||
* @param json uploaded JSON data
|
||||
* @param args array of arguments, needs to be of length @e args_expected
|
||||
* @param args array of arguments, needs to be of length @e nargs
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
@ -225,7 +237,7 @@ struct TEH_RequestHandler
|
||||
* Function to call to handle DELETE requests.
|
||||
*
|
||||
* @param rc context for the request
|
||||
* @param args array of arguments, needs to be of length @e args_expected
|
||||
* @param args array of arguments, needs to be of length @e nargs
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
@ -234,17 +246,6 @@ 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).
|
||||
*/
|
||||
@ -264,6 +265,17 @@ 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;
|
||||
};
|
||||
|
||||
|
||||
|
395
src/exchange/taler-exchange-httpd_age-withdraw.c
Normal file
395
src/exchange/taler-exchange-httpd_age-withdraw.c
Normal file
@ -0,0 +1,395 @@
|
||||
/*
|
||||
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 */
|
47
src/exchange/taler-exchange-httpd_age-withdraw.h
Normal file
47
src/exchange/taler-exchange-httpd_age-withdraw.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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
|
1076
src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
Normal file
1076
src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
Normal file
File diff suppressed because it is too large
Load Diff
56
src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
Normal file
56
src/exchange/taler-exchange-httpd_age-withdraw_reveal.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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
|
236
src/exchange/taler-exchange-httpd_aml-decision-get.c
Normal file
236
src/exchange/taler-exchange-httpd_aml-decision-get.c
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 */
|
374
src/exchange/taler-exchange-httpd_aml-decision.c
Normal file
374
src/exchange/taler-exchange-httpd_aml-decision.c
Normal file
@ -0,0 +1,374 @@
|
||||
/*
|
||||
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 */
|
79
src/exchange/taler-exchange-httpd_aml-decision.h
Normal file
79
src/exchange/taler-exchange-httpd_aml-decision.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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
|
211
src/exchange/taler-exchange-httpd_aml-decisions-get.c
Normal file
211
src/exchange/taler-exchange-httpd_aml-decisions-get.c
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
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 */
|
@ -233,9 +233,8 @@ 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));
|
||||
}
|
||||
@ -268,13 +267,12 @@ 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;
|
||||
}
|
||||
@ -295,9 +293,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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
@ -77,6 +77,11 @@ struct BatchWithdrawContext
|
||||
*/
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub;
|
||||
|
||||
/**
|
||||
* request context
|
||||
*/
|
||||
const struct TEH_RequestContext *rc;
|
||||
|
||||
/**
|
||||
* KYC status of the reserve used for the operation.
|
||||
*/
|
||||
@ -108,6 +113,11 @@ struct BatchWithdrawContext
|
||||
*/
|
||||
unsigned int planchets_length;
|
||||
|
||||
/**
|
||||
* AML decision, #TALER_AML_NORMAL if we may proceed.
|
||||
*/
|
||||
enum TALER_AmlDecisionState aml_decision;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -150,6 +160,127 @@ 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
|
||||
@ -177,14 +308,116 @@ batch_withdraw_transaction (void *cls,
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
bool balance_ok = false;
|
||||
bool found = false;
|
||||
const char *kyc_required;
|
||||
char *kyc_required;
|
||||
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->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,
|
||||
@ -193,22 +426,44 @@ batch_withdraw_transaction (void *cls,
|
||||
NULL);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
kyc_required = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = 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);
|
||||
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;
|
||||
}
|
||||
if (NULL != kyc_required)
|
||||
{
|
||||
/* insert KYC requirement into DB! */
|
||||
wc->kyc.ok = false;
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
qs = 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,
|
||||
@ -221,10 +476,13 @@ 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)
|
||||
@ -288,12 +546,18 @@ batch_withdraw_transaction (void *cls,
|
||||
if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
|
||||
(conflict) )
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (nonce_reuse)
|
||||
@ -311,96 +575,6 @@ 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.
|
||||
@ -528,8 +702,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
|
||||
ksh = TEH_keys_get_state ();
|
||||
if (NULL == ksh)
|
||||
{
|
||||
if (! check_request_idempotent (rc,
|
||||
wc,
|
||||
if (! check_request_idempotent (wc,
|
||||
&mret))
|
||||
{
|
||||
return TALER_MHD_reply_with_error (rc->connection,
|
||||
@ -550,8 +723,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
|
||||
NULL);
|
||||
if (NULL == dk)
|
||||
{
|
||||
if (! check_request_idempotent (rc,
|
||||
wc,
|
||||
if (! check_request_idempotent (wc,
|
||||
&mret))
|
||||
{
|
||||
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
|
||||
@ -563,8 +735,7 @@ 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 (rc,
|
||||
wc,
|
||||
if (! check_request_idempotent (wc,
|
||||
&mret))
|
||||
{
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
@ -588,8 +759,7 @@ parse_planchets (const struct TEH_RequestContext *rc,
|
||||
if (dk->recoup_possible)
|
||||
{
|
||||
/* This denomination has been revoked */
|
||||
if (! check_request_idempotent (rc,
|
||||
wc,
|
||||
if (! check_request_idempotent (wc,
|
||||
&mret))
|
||||
{
|
||||
return TEH_RESPONSE_reply_expired_denom_pub_hash (
|
||||
@ -669,7 +839,10 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub,
|
||||
const json_t *root)
|
||||
{
|
||||
struct BatchWithdrawContext wc;
|
||||
struct BatchWithdrawContext wc = {
|
||||
.reserve_pub = reserve_pub,
|
||||
.rc = rc
|
||||
};
|
||||
json_t *planchets;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_json ("planchets",
|
||||
@ -677,13 +850,9 @@ 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;
|
||||
|
||||
|
55
src/exchange/taler-exchange-httpd_config.c
Normal file
55
src/exchange/taler-exchange-httpd_config.c
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 */
|
58
src/exchange/taler-exchange-httpd_config.h
Normal file
58
src/exchange/taler-exchange-httpd_config.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
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
|
@ -234,12 +234,10 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
|
||||
.cipher = TALER_DENOMINATION_CS
|
||||
};
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
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_fixed_auto ("nonce",
|
||||
&nonce),
|
||||
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
|
||||
&denom_pub_hash),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
struct TEH_DenominationKey *dk;
|
||||
@ -333,17 +331,11 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
return TALER_MHD_REPLY_JSON_PACK (
|
||||
rc->connection,
|
||||
MHD_HTTP_OK,
|
||||
TALER_JSON_pack_exchange_withdraw_values ("ewv",
|
||||
&ewv));
|
||||
}
|
||||
|
||||
|
||||
|
@ -90,6 +90,11 @@ 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).
|
||||
@ -128,6 +133,7 @@ reply_deposit_details (
|
||||
&pub,
|
||||
&sig)))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return TALER_MHD_reply_with_ec (connection,
|
||||
ec,
|
||||
NULL);
|
||||
@ -184,7 +190,8 @@ deposits_get_transaction (void *cls,
|
||||
&ctx->execution_time,
|
||||
&ctx->coin_contribution,
|
||||
&fee,
|
||||
&ctx->kyc);
|
||||
&ctx->kyc,
|
||||
&ctx->aml_decision);
|
||||
if (0 > qs)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
@ -257,6 +264,8 @@ 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",
|
||||
|
@ -150,12 +150,11 @@ 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
|
||||
|
@ -25,6 +25,7 @@
|
||||
#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"
|
||||
@ -44,24 +45,6 @@
|
||||
#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.
|
||||
*/
|
||||
@ -755,7 +738,7 @@ free_denom_cb (void *cls,
|
||||
* @param value the `struct HelperSignkey` to release
|
||||
* @return #GNUNET_OK (continue to iterate)
|
||||
*/
|
||||
static int
|
||||
static enum GNUNET_GenericReturnValue
|
||||
free_esign_cb (void *cls,
|
||||
const struct GNUNET_PeerIdentity *pid,
|
||||
void *value)
|
||||
@ -1922,6 +1905,7 @@ 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;
|
||||
@ -1955,7 +1939,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
|
||||
json_t *sig;
|
||||
int r;
|
||||
|
||||
r = json_object_set (
|
||||
r = json_object_set_new (
|
||||
keys,
|
||||
"extensions",
|
||||
extensions);
|
||||
@ -2275,9 +2259,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
|
||||
|
||||
if (age_restricted)
|
||||
{
|
||||
int r = json_object_set (group->json,
|
||||
"age_mask",
|
||||
json_integer (meta.age_mask.bits));
|
||||
int r = json_object_set_new (group->json,
|
||||
"age_mask",
|
||||
json_integer (meta.age_mask.bits));
|
||||
GNUNET_assert (0 == r);
|
||||
}
|
||||
|
||||
@ -2285,8 +2269,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
|
||||
list = json_array ();
|
||||
GNUNET_assert (NULL != list);
|
||||
GNUNET_assert (0 ==
|
||||
json_object_set (group->json, denoms_key, list));
|
||||
|
||||
json_object_set_new (group->json,
|
||||
denoms_key,
|
||||
list));
|
||||
GNUNET_assert (
|
||||
GNUNET_OK ==
|
||||
GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
|
||||
@ -2370,7 +2355,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 (
|
||||
json_object_set_new (
|
||||
group->json,
|
||||
"hash",
|
||||
GNUNET_JSON_PACK (
|
||||
@ -2392,9 +2377,10 @@ 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);
|
||||
@ -2418,6 +2404,7 @@ 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;
|
||||
@ -2430,6 +2417,7 @@ 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);
|
||||
@ -3629,6 +3617,7 @@ 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,
|
||||
@ -3641,7 +3630,6 @@ 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,
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2021-2022 Taler Systems SA
|
||||
Copyright (C) 2021-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
|
||||
@ -112,6 +112,11 @@ 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.
|
||||
*/
|
||||
@ -233,7 +238,7 @@ initiate_cb (
|
||||
kyp->ih = NULL;
|
||||
kyp->ih_done = true;
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"KYC initiation completed with status %d (%s)\n",
|
||||
"KYC initiation completed with ec=%d (%s)\n",
|
||||
ec,
|
||||
(TALER_EC_NONE == ec)
|
||||
? redirect_url
|
||||
@ -297,11 +302,13 @@ 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)
|
||||
{
|
||||
@ -330,12 +337,26 @@ kyc_check (void *cls,
|
||||
GNUNET_free (requirements);
|
||||
return GNUNET_DB_STATUS_HARD_ERROR;
|
||||
}
|
||||
if (TALER_KYCLOGIC_check_satisfied (
|
||||
requirements,
|
||||
&h_payto,
|
||||
&kyp->kyc_details,
|
||||
TEH_plugin->select_satisfied_kyc_processes,
|
||||
TEH_plugin->cls))
|
||||
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)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"KYC requirements `%s' already satisfied\n",
|
||||
@ -374,6 +395,17 @@ 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);
|
||||
@ -554,6 +586,17 @@ 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",
|
||||
@ -602,6 +645,8 @@ 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));
|
||||
}
|
||||
@ -639,6 +684,8 @@ 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",
|
||||
|
@ -24,6 +24,7 @@
|
||||
#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"
|
||||
@ -169,6 +170,7 @@ TEH_kyc_proof_cleanup (void)
|
||||
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
|
||||
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
|
||||
* @param expiration until when is the KYC check valid
|
||||
* @param attributes user attributes returned by the provider
|
||||
* @param http_status HTTP status code of @a response
|
||||
* @param[in] response to return to the HTTP client
|
||||
*/
|
||||
@ -179,6 +181,7 @@ proof_cb (
|
||||
const char *provider_user_id,
|
||||
const char *provider_legitimization_id,
|
||||
struct GNUNET_TIME_Absolute expiration,
|
||||
const json_t *attributes,
|
||||
unsigned int http_status,
|
||||
struct MHD_Response *response)
|
||||
{
|
||||
@ -193,7 +196,41 @@ 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,
|
||||
|
@ -54,7 +54,7 @@ struct KycRequestContext
|
||||
/**
|
||||
* Name of the required check.
|
||||
*/
|
||||
const char *required;
|
||||
char *required;
|
||||
|
||||
};
|
||||
|
||||
@ -109,22 +109,34 @@ wallet_kyc_check (void *cls,
|
||||
struct KycRequestContext *krc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
krc->required = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = 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);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"KYC check required at %s is `%s'\n",
|
||||
TALER_amount2s (&krc->balance),
|
||||
krc->required);
|
||||
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;
|
||||
}
|
||||
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,
|
||||
@ -225,6 +237,7 @@ TEH_handler_kyc_wallet (
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
GNUNET_free (krc.required);
|
||||
return TEH_RESPONSE_reply_kyc_required (rc->connection,
|
||||
&krc.h_payto,
|
||||
&krc.kyc);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#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"
|
||||
@ -140,8 +141,7 @@ TEH_kyc_webhook_cleanup (void)
|
||||
|
||||
|
||||
/**
|
||||
* Function called with the result of a webhook
|
||||
* operation.
|
||||
* Function called with the result of a KYC webhook operation.
|
||||
*
|
||||
* Note that the "decref" for the @a response
|
||||
* will be done by the plugin.
|
||||
@ -154,6 +154,7 @@ 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
|
||||
*/
|
||||
@ -167,6 +168,7 @@ 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)
|
||||
{
|
||||
@ -179,7 +181,39 @@ 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,
|
||||
@ -262,11 +296,12 @@ handler_kyc_webhook_generic (
|
||||
rc->rh_ctx = kwh;
|
||||
rc->rh_cleaner = &clean_kwh;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TALER_KYCLOGIC_lookup_logic (args[0],
|
||||
&kwh->plugin,
|
||||
&kwh->pd,
|
||||
&kwh->provider_section))
|
||||
if ( (NULL == args[0]) ||
|
||||
(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",
|
||||
@ -274,7 +309,7 @@ handler_kyc_webhook_generic (
|
||||
return TALER_MHD_reply_with_error (rc->connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
|
||||
"$NAME");
|
||||
args[0]);
|
||||
}
|
||||
kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
|
||||
kwh->pd,
|
||||
|
@ -174,6 +174,32 @@ TEH_handler_management_post_drain (
|
||||
const json_t *root);
|
||||
|
||||
|
||||
/**
|
||||
* Handle a POST "/management/aml-officers" request.
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param root uploaded JSON data
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_handler_management_aml_officers (
|
||||
struct MHD_Connection *connection,
|
||||
const json_t *root);
|
||||
|
||||
|
||||
/**
|
||||
* Handle a POST "/management/partners" request.
|
||||
*
|
||||
* @param connection the MHD connection to handle
|
||||
* @param root uploaded JSON data
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_handler_management_partners (
|
||||
struct MHD_Connection *connection,
|
||||
const json_t *root);
|
||||
|
||||
|
||||
/**
|
||||
* Initialize extension configuration handling.
|
||||
*
|
||||
|
142
src/exchange/taler-exchange-httpd_management_aml-officers.c
Normal file
142
src/exchange/taler-exchange-httpd_management_aml-officers.c
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 */
|
132
src/exchange/taler-exchange-httpd_management_partners.c
Normal file
132
src/exchange/taler-exchange-httpd_management_partners.c
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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 */
|
@ -34,18 +34,20 @@ enum TEH_MetricTypeRequest
|
||||
TEH_MT_REQUEST_OTHER = 0,
|
||||
TEH_MT_REQUEST_DEPOSIT = 1,
|
||||
TEH_MT_REQUEST_WITHDRAW = 2,
|
||||
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! */
|
||||
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! */
|
||||
};
|
||||
|
||||
/**
|
||||
@ -55,10 +57,12 @@ enum TEH_MetricTypeSuccess
|
||||
{
|
||||
TEH_MT_SUCCESS_DEPOSIT = 0,
|
||||
TEH_MT_SUCCESS_WITHDRAW = 1,
|
||||
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! */
|
||||
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! */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -435,6 +435,8 @@ 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)),
|
||||
|
@ -280,23 +280,47 @@ merge_transaction (void *cls,
|
||||
bool in_conflict = true;
|
||||
bool no_balance = true;
|
||||
bool no_partner = true;
|
||||
const char *required;
|
||||
char *required;
|
||||
|
||||
required = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = 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);
|
||||
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;
|
||||
}
|
||||
if (NULL != required)
|
||||
{
|
||||
pcc->kyc.ok = false;
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
qs = 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 (
|
||||
@ -314,8 +338,7 @@ merge_transaction (void *cls,
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
TALER_LOG_WARNING (
|
||||
"Failed to store merge purse information in database\n");
|
||||
GNUNET_break (0);
|
||||
*mhd_ret =
|
||||
TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
|
@ -773,12 +773,17 @@ 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;
|
||||
@ -795,12 +800,7 @@ 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,
|
||||
|
@ -76,14 +76,14 @@ struct ReserveAttestContext
|
||||
struct TALER_ReserveSignatureP reserve_sig;
|
||||
|
||||
/**
|
||||
* Attributes we are affirming.
|
||||
* Attributes we are affirming. JSON object.
|
||||
*/
|
||||
json_t *json_attest;
|
||||
|
||||
/**
|
||||
* Error code encountered in interaction with KYC provider.
|
||||
* Database error codes encountered.
|
||||
*/
|
||||
enum TALER_ErrorCode ec;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
/**
|
||||
* Set to true if we did not find the reserve.
|
||||
@ -140,8 +140,12 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
|
||||
&exchange_sig),
|
||||
GNUNET_JSON_pack_data_auto ("exchange_pub",
|
||||
&exchange_pub),
|
||||
GNUNET_JSON_pack_array_steal ("attest",
|
||||
rhc->json_attest));
|
||||
GNUNET_JSON_pack_timestamp ("exchange_timestamp",
|
||||
now),
|
||||
GNUNET_JSON_pack_timestamp ("expiration_time",
|
||||
rhc->etime),
|
||||
GNUNET_JSON_pack_object_steal ("attributes",
|
||||
rhc->json_attest));
|
||||
}
|
||||
|
||||
|
||||
@ -152,68 +156,71 @@ reply_reserve_attest_success (struct MHD_Connection *connection,
|
||||
* set based on the details requested by the client.
|
||||
*
|
||||
* @param cls our `struct ReserveAttestContext *`
|
||||
* @param provider_section KYC provider configuration section
|
||||
* @param provider_user_id UID at a provider (can be NULL)
|
||||
* @param legi_id legitimization process ID (can be NULL)
|
||||
* @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_process_cb (void *cls,
|
||||
const struct TALER_PaytoHashP *h_payto,
|
||||
const char *provider_section,
|
||||
const char *provider_user_id,
|
||||
const char *legi_id)
|
||||
const char *birthdate,
|
||||
struct GNUNET_TIME_Timestamp collection_time,
|
||||
struct GNUNET_TIME_Timestamp expiration_time,
|
||||
size_t enc_attributes_size,
|
||||
const void *enc_attributes)
|
||||
{
|
||||
struct ReserveAttestContext *rsc = cls;
|
||||
struct GNUNET_TIME_Timestamp etime;
|
||||
json_t *attrs;
|
||||
json_t *val;
|
||||
const char *name;
|
||||
bool match = false;
|
||||
|
||||
rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
|
||||
provider_user_id,
|
||||
legi_id,
|
||||
&etime,
|
||||
&attrs);
|
||||
if (TALER_EC_NONE != rsc->ec)
|
||||
if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
|
||||
return;
|
||||
if (GNUNET_TIME_absolute_is_past (etime.abs_time))
|
||||
attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
|
||||
enc_attributes,
|
||||
enc_attributes_size);
|
||||
json_object_foreach (attrs, name, val)
|
||||
{
|
||||
json_decref (attrs);
|
||||
return;
|
||||
}
|
||||
{
|
||||
json_t *val;
|
||||
const char *name;
|
||||
bool requested = false;
|
||||
size_t idx;
|
||||
json_t *str;
|
||||
|
||||
json_object_foreach (attrs, name, val)
|
||||
if (NULL != json_object_get (rsc->json_attest,
|
||||
name))
|
||||
continue; /* duplicate */
|
||||
json_array_foreach (rsc->details, idx, str)
|
||||
{
|
||||
bool requested = false;
|
||||
size_t idx;
|
||||
json_t *str;
|
||||
|
||||
if (NULL != json_object_get (rsc->json_attest,
|
||||
name))
|
||||
continue; /* duplicate */
|
||||
json_array_foreach (rsc->details, idx, str)
|
||||
if (0 == strcmp (json_string_value (str),
|
||||
name))
|
||||
{
|
||||
if (0 == strcmp (json_string_value (str),
|
||||
name))
|
||||
{
|
||||
requested = true;
|
||||
break;
|
||||
}
|
||||
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 (etime,
|
||||
rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
|
||||
rsc->etime);
|
||||
}
|
||||
|
||||
@ -241,9 +248,9 @@ reserve_attest_transaction (void *cls,
|
||||
struct ReserveAttestContext *rsc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
rsc->json_attest = json_array ();
|
||||
rsc->json_attest = json_object ();
|
||||
GNUNET_assert (NULL != rsc->json_attest);
|
||||
qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
|
||||
qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
|
||||
&rsc->h_payto,
|
||||
&kyc_process_cb,
|
||||
rsc);
|
||||
@ -255,7 +262,7 @@ reserve_attest_transaction (void *cls,
|
||||
= TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"iterate_kyc_reference");
|
||||
"select_kyc_attributes");
|
||||
return qs;
|
||||
case GNUNET_DB_STATUS_SOFT_ERROR:
|
||||
GNUNET_break (0);
|
||||
@ -373,16 +380,8 @@ TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
|
||||
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
|
||||
args[0]);
|
||||
}
|
||||
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;
|
||||
return reply_reserve_attest_success (rc->connection,
|
||||
&rsc);
|
||||
}
|
||||
|
||||
|
||||
|
@ -178,14 +178,13 @@ 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,
|
||||
&balance,
|
||||
&rcc->balance,
|
||||
&payto_uri);
|
||||
switch (qs)
|
||||
{
|
||||
@ -226,19 +225,34 @@ reserve_close_transaction (void *cls,
|
||||
(0 != strcmp (payto_uri,
|
||||
rcc->payto_uri)) ) )
|
||||
{
|
||||
const char *kyc_needed;
|
||||
/* 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;
|
||||
|
||||
TALER_payto_hash (rcc->payto_uri,
|
||||
&rcc->kyc_payto);
|
||||
rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
|
||||
kyc_needed
|
||||
= TALER_KYCLOGIC_kyc_test_required (
|
||||
TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
|
||||
&rcc->kyc_payto,
|
||||
TEH_plugin->select_satisfied_kyc_processes,
|
||||
TEH_plugin->cls,
|
||||
&amount_it,
|
||||
rcc);
|
||||
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;
|
||||
}
|
||||
if (rcc->qs < 0)
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs)
|
||||
@ -251,12 +265,26 @@ reserve_close_transaction (void *cls,
|
||||
"iterate_reserve_close_info");
|
||||
return qs;
|
||||
}
|
||||
rcc->kyc.ok = false;
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
TEH_plugin->cls,
|
||||
kyc_needed,
|
||||
&rcc->kyc_payto,
|
||||
&rcc->kyc.requirement_row);
|
||||
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 = true;
|
||||
@ -284,7 +312,7 @@ reserve_close_transaction (void *cls,
|
||||
|
||||
if (0 >
|
||||
TALER_amount_subtract (&rcc->wire_amount,
|
||||
&balance,
|
||||
&rcc->balance,
|
||||
&wf->closing))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
@ -302,7 +330,7 @@ reserve_close_transaction (void *cls,
|
||||
payto_uri,
|
||||
&rcc->reserve_sig,
|
||||
rcc->timestamp,
|
||||
&balance,
|
||||
&rcc->balance,
|
||||
&wf->closing);
|
||||
GNUNET_free (payto_uri);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
@ -62,6 +62,16 @@ 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.
|
||||
*/
|
||||
@ -84,13 +94,10 @@ static struct ReservePoller *rp_tail;
|
||||
void
|
||||
TEH_reserves_get_cleanup ()
|
||||
{
|
||||
struct ReservePoller *rp;
|
||||
|
||||
while (NULL != (rp = rp_head))
|
||||
for (struct ReservePoller *rp = rp_head;
|
||||
NULL != rp;
|
||||
rp = rp->next)
|
||||
{
|
||||
GNUNET_CONTAINER_DLL_remove (rp_head,
|
||||
rp_tail,
|
||||
rp);
|
||||
if (rp->suspended)
|
||||
{
|
||||
rp->suspended = false;
|
||||
@ -115,11 +122,14 @@ rp_cleanup (struct TEH_RequestContext *rc)
|
||||
if (NULL != rp->eh)
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Cancelling DB event listening\n");
|
||||
"Cancelling DB event listening on cleanup (odd unless during shutdown)\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);
|
||||
}
|
||||
|
||||
@ -137,26 +147,15 @@ db_event_cb (void *cls,
|
||||
const void *extra,
|
||||
size_t extra_size)
|
||||
{
|
||||
struct TEH_RequestContext *rc = cls;
|
||||
struct ReservePoller *rp = rc->rh_ctx;
|
||||
struct ReservePoller *rp = cls;
|
||||
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 */
|
||||
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);
|
||||
rp->suspended = false;
|
||||
MHD_resume_connection (rp->connection);
|
||||
TALER_MHD_daemon_trigger ();
|
||||
TEH_check_invariants ();
|
||||
@ -164,191 +163,133 @@ 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 ReserveHistoryContext rsc;
|
||||
struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
|
||||
struct GNUNET_DB_EventHandler *eh = NULL;
|
||||
struct ReservePoller *rp = rc->rh_ctx;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_STRINGS_string_to_data (args[0],
|
||||
strlen (args[0]),
|
||||
&rsc.reserve_pub,
|
||||
sizeof (rsc.reserve_pub)))
|
||||
if (NULL == rp)
|
||||
{
|
||||
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;
|
||||
struct GNUNET_TIME_Relative timeout
|
||||
= GNUNET_TIME_UNIT_ZERO;
|
||||
|
||||
long_poll_timeout_ms
|
||||
= MHD_lookup_connection_value (rc->connection,
|
||||
MHD_GET_ARGUMENT_KIND,
|
||||
"timeout_ms");
|
||||
if (NULL != long_poll_timeout_ms)
|
||||
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)))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
if ( (! GNUNET_TIME_relative_is_zero (timeout)) &&
|
||||
(NULL == rc->rh_ctx) )
|
||||
|
||||
if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
|
||||
(NULL == rp->eh) )
|
||||
{
|
||||
struct TALER_ReserveEventP rep = {
|
||||
.header.size = htons (sizeof (rep)),
|
||||
.header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
|
||||
.reserve_pub = rsc.reserve_pub
|
||||
.reserve_pub = rp->reserve_pub
|
||||
};
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Starting DB event listening\n");
|
||||
eh = TEH_plugin->event_listen (TEH_plugin->cls,
|
||||
timeout,
|
||||
&rep.header,
|
||||
&db_event_cb,
|
||||
rc);
|
||||
"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);
|
||||
}
|
||||
{
|
||||
MHD_RESULT mhd_ret;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
TEH_DB_run_transaction (rc->connection,
|
||||
"get reserve balance",
|
||||
TEH_MT_REQUEST_OTHER,
|
||||
&mhd_ret,
|
||||
&reserve_balance_transaction,
|
||||
&rsc))
|
||||
{
|
||||
if (NULL != eh)
|
||||
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
|
||||
eh);
|
||||
return mhd_ret;
|
||||
}
|
||||
}
|
||||
/* generate proper response */
|
||||
if (rsc.not_found)
|
||||
{
|
||||
struct ReservePoller *rp = rc->rh_ctx;
|
||||
|
||||
if ( (NULL != rp) ||
|
||||
(GNUNET_TIME_relative_is_zero (timeout)) )
|
||||
qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
|
||||
&rp->reserve_pub,
|
||||
&rp->balance);
|
||||
switch (qs)
|
||||
{
|
||||
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_NOT_FOUND,
|
||||
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
|
||||
args[0]);
|
||||
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;
|
||||
}
|
||||
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));
|
||||
GNUNET_break (0);
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
|
||||
|
@ -50,11 +50,6 @@ 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.
|
||||
*/
|
||||
@ -67,53 +62,55 @@ struct ReserveAttestContext
|
||||
* legitimization processes for the given user.
|
||||
*
|
||||
* @param cls our `struct ReserveAttestContext *`
|
||||
* @param provider_section KYC provider configuration section
|
||||
* @param provider_user_id UID at a provider (can be NULL)
|
||||
* @param legi_id legitimization process ID (can be NULL)
|
||||
* @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_process_cb (void *cls,
|
||||
const struct TALER_PaytoHashP *h_payto,
|
||||
const char *provider_section,
|
||||
const char *provider_user_id,
|
||||
const char *legi_id)
|
||||
const char *birthdate,
|
||||
struct GNUNET_TIME_Timestamp collection_time,
|
||||
struct GNUNET_TIME_Timestamp expiration_time,
|
||||
size_t enc_attributes_size,
|
||||
const void *enc_attributes)
|
||||
{
|
||||
struct ReserveAttestContext *rsc = cls;
|
||||
struct GNUNET_TIME_Timestamp etime;
|
||||
json_t *attrs;
|
||||
json_t *val;
|
||||
const char *name;
|
||||
|
||||
rsc->ec = TALER_KYCLOGIC_user_to_attributes (provider_section,
|
||||
provider_user_id,
|
||||
legi_id,
|
||||
&etime,
|
||||
&attrs);
|
||||
if (TALER_EC_NONE != rsc->ec)
|
||||
if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
|
||||
return;
|
||||
|
||||
attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
|
||||
enc_attributes,
|
||||
enc_attributes_size);
|
||||
json_object_foreach (attrs, name, val)
|
||||
{
|
||||
json_t *val;
|
||||
const char *name;
|
||||
bool duplicate = false;
|
||||
size_t idx;
|
||||
json_t *str;
|
||||
|
||||
json_object_foreach (attrs, name, val)
|
||||
json_array_foreach (rsc->attributes, idx, str)
|
||||
{
|
||||
bool duplicate = false;
|
||||
size_t idx;
|
||||
json_t *str;
|
||||
|
||||
json_array_foreach (rsc->attributes, idx, str)
|
||||
if (0 == strcmp (json_string_value (str),
|
||||
name))
|
||||
{
|
||||
if (0 == strcmp (json_string_value (str),
|
||||
name))
|
||||
{
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +141,7 @@ reserve_attest_transaction (void *cls,
|
||||
|
||||
rsc->attributes = json_array ();
|
||||
GNUNET_assert (NULL != rsc->attributes);
|
||||
qs = TEH_plugin->iterate_kyc_reference (TEH_plugin->cls,
|
||||
qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
|
||||
&rsc->h_payto,
|
||||
&kyc_process_cb,
|
||||
rsc);
|
||||
@ -213,6 +210,7 @@ TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
|
||||
&rsc))
|
||||
{
|
||||
json_decref (rsc.attributes);
|
||||
rsc.attributes = NULL;
|
||||
return mhd_ret;
|
||||
}
|
||||
}
|
||||
@ -220,23 +218,17 @@ 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_object_steal ("attributes",
|
||||
rsc.attributes));
|
||||
GNUNET_JSON_pack_array_steal ("details",
|
||||
rsc.attributes));
|
||||
}
|
||||
|
||||
|
||||
|
@ -189,24 +189,47 @@ purse_transaction (void *cls,
|
||||
{
|
||||
struct ReservePurseContext *rpc = cls;
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
char *required;
|
||||
|
||||
const char *required;
|
||||
|
||||
required = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = 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);
|
||||
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;
|
||||
}
|
||||
if (NULL != required)
|
||||
{
|
||||
rpc->kyc.ok = false;
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
qs = 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;
|
||||
|
||||
@ -230,8 +253,7 @@ purse_transaction (void *cls,
|
||||
{
|
||||
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
|
||||
return qs;
|
||||
TALER_LOG_WARNING (
|
||||
"Failed to store purse purse information in database\n");
|
||||
GNUNET_break (0);
|
||||
*mhd_ret =
|
||||
TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU Affero General Public License as published by the Free Software
|
||||
@ -1142,4 +1142,29 @@ 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 */
|
||||
|
@ -91,6 +91,19 @@ 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.
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
@ -61,16 +61,21 @@ struct WithdrawContext
|
||||
struct TALER_EXCHANGEDB_KycStatus kyc;
|
||||
|
||||
/**
|
||||
* Hash of the payto-URI representing the reserve
|
||||
* from which we are withdrawing.
|
||||
* Hash of the payto-URI representing the account
|
||||
* from which the money was put into the reserve.
|
||||
*/
|
||||
struct TALER_PaytoHashP h_payto;
|
||||
struct TALER_PaytoHashP h_account_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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -108,7 +113,7 @@ withdraw_amount_cb (void *cls,
|
||||
return;
|
||||
qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
|
||||
TEH_plugin->cls,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_payto,
|
||||
limit,
|
||||
cb,
|
||||
cb_cls);
|
||||
@ -120,6 +125,34 @@ 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
|
||||
@ -150,36 +183,153 @@ 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_payto);
|
||||
&wc->h_account_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)
|
||||
{
|
||||
const char *kyc_required;
|
||||
char *kyc_required;
|
||||
|
||||
kyc_required = TALER_KYCLOGIC_kyc_test_required (
|
||||
qs = TALER_KYCLOGIC_kyc_test_required (
|
||||
TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_payto,
|
||||
TEH_plugin->select_satisfied_kyc_processes,
|
||||
TEH_plugin->cls,
|
||||
&withdraw_amount_cb,
|
||||
wc);
|
||||
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;
|
||||
}
|
||||
if (NULL != kyc_required)
|
||||
{
|
||||
/* insert KYC requirement into DB! */
|
||||
wc->kyc.ok = false;
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
qs = TEH_plugin->insert_kyc_requirement_for_account (
|
||||
TEH_plugin->cls,
|
||||
kyc_required,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_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;
|
||||
@ -198,10 +348,13 @@ 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)
|
||||
@ -499,8 +652,13 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
|
||||
if (! wc.kyc.ok)
|
||||
return TEH_RESPONSE_reply_kyc_required (rc->connection,
|
||||
&wc.h_payto,
|
||||
&wc.h_account_payto,
|
||||
&wc.kyc);
|
||||
|
||||
if (TALER_AML_NORMAL != wc.aml_decision)
|
||||
return TEH_RESPONSE_reply_aml_blocked (rc->connection,
|
||||
wc.aml_decision);
|
||||
|
||||
{
|
||||
MHD_RESULT ret;
|
||||
|
||||
|
@ -57,6 +57,12 @@ 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?
|
||||
*/
|
||||
@ -472,9 +478,9 @@ transaction_completed (void)
|
||||
GNUNET_SCHEDULER_shutdown ();
|
||||
return;
|
||||
}
|
||||
if (! hh_returned_data)
|
||||
if (! (hh_returned_data || hh_account_404) )
|
||||
{
|
||||
/* 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;
|
||||
@ -482,8 +488,17 @@ 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);
|
||||
@ -495,11 +510,13 @@ 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 (const struct TALER_BANK_CreditDetails *details,
|
||||
process_reply (unsigned int wrap_size,
|
||||
const struct TALER_BANK_CreditDetails *details,
|
||||
unsigned int details_length)
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qs;
|
||||
@ -545,416 +562,31 @@ process_reply (const struct TALER_BANK_CreditDetails *details,
|
||||
}
|
||||
lroff = cd->serial_id;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
db_plugin->start_read_committed (db_plugin->cls,
|
||||
"wirewatch check for incoming wire transfers"))
|
||||
if (0 != details_length)
|
||||
{
|
||||
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];
|
||||
enum GNUNET_DB_QueryStatus qss[details_length];
|
||||
struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
|
||||
|
||||
/* 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.) */
|
||||
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->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! */
|
||||
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 (0 != details_length)
|
||||
{
|
||||
enum GNUNET_DB_QueryStatus qss[details_length];
|
||||
struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
|
||||
|
||||
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->batch2_reserves_in_insert (db_plugin->cls,
|
||||
reserves,
|
||||
details_length,
|
||||
batch_size,
|
||||
qss);
|
||||
reserves,
|
||||
details_length,
|
||||
wrap_size,
|
||||
qss);
|
||||
switch (qs)
|
||||
{
|
||||
case GNUNET_DB_STATUS_HARD_ERROR:
|
||||
@ -964,7 +596,7 @@ process_reply_batched2 (unsigned int batch_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",
|
||||
batch_size);
|
||||
wrap_size);
|
||||
handle_soft_error ();
|
||||
return;
|
||||
default:
|
||||
@ -1069,56 +701,44 @@ static void
|
||||
history_cb (void *cls,
|
||||
const struct TALER_BANK_CreditHistoryResponse *reply)
|
||||
{
|
||||
static int batch_mode = -2;
|
||||
static int wrap_size = -2;
|
||||
|
||||
(void) cls;
|
||||
if (-2 == batch_mode)
|
||||
if (-2 == wrap_size)
|
||||
{
|
||||
const char *mode = getenv ("TALER_USE_BATCH");
|
||||
const char *mode = getenv ("TALER_WIREWATCH_WARP_SIZE");
|
||||
char dummy;
|
||||
|
||||
if ( (NULL == mode) ||
|
||||
(1 != sscanf (mode,
|
||||
"%d%c",
|
||||
&batch_mode,
|
||||
&wrap_size,
|
||||
&dummy)) )
|
||||
{
|
||||
if (NULL != mode)
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Bad batch mode `%s' specified\n",
|
||||
mode);
|
||||
batch_mode = -1;
|
||||
wrap_size = 8; /* maximum supported is currently 8 */
|
||||
}
|
||||
}
|
||||
GNUNET_assert (NULL == task);
|
||||
hh = NULL;
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
|
||||
"History request returned with HTTP status %u\n",
|
||||
reply->http_status);
|
||||
switch (reply->http_status)
|
||||
{
|
||||
case MHD_HTTP_OK:
|
||||
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;
|
||||
}
|
||||
process_reply (wrap_size,
|
||||
reply->details.success.details,
|
||||
reply->details.success.details_length);
|
||||
return;
|
||||
case MHD_HTTP_NO_CONTENT:
|
||||
transaction_completed ();
|
||||
return;
|
||||
case MHD_HTTP_NOT_FOUND:
|
||||
hh_account_404 = true;
|
||||
if (ignore_account_404)
|
||||
{
|
||||
transaction_completed ();
|
||||
@ -1153,10 +773,11 @@ continue_with_shard (void *cls)
|
||||
shard_end - latest_row_off);
|
||||
GNUNET_assert (NULL == hh);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Requesting credit history staring from %llu\n",
|
||||
"Requesting credit history starting 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,
|
||||
@ -1253,6 +874,17 @@ 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);
|
||||
@ -1265,7 +897,7 @@ lock_shard (void *cls)
|
||||
job_name,
|
||||
GNUNET_STRINGS_relative_time_to_string (
|
||||
wirewatch_idle_sleep_interval,
|
||||
GNUNET_YES));
|
||||
true));
|
||||
delayed_until = GNUNET_TIME_relative_to_absolute (
|
||||
wirewatch_idle_sleep_interval);
|
||||
shard_open = false;
|
||||
|
@ -7,6 +7,7 @@ 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
|
||||
|
18
src/exchangedb/.gitignore
vendored
18
src/exchangedb/.gitignore
vendored
@ -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
|
||||
shard-drop0001.sqltest-exchangedb-by-j-postgres
|
||||
test-exchangedb-by-j-postgres
|
||||
perf-exchangedb-reserves-in-insert-postgres
|
||||
perf_deposits_get_ready-postgres
|
||||
perf_get_link_data-postgres
|
||||
perf_reserves_in_insert-postgres
|
||||
perf_select_refunds_by_coin-postgres
|
||||
exchange-0002.sql
|
||||
procedures.sql
|
||||
exchange-0003.sql
|
||||
perf-exchangedb-reserves-in-insert-postgres
|
||||
test-exchangedb-batch-reserves-in-insert-postgres
|
||||
test-exchangedb-populate-table-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
|
@ -1,6 +1,6 @@
|
||||
--
|
||||
-- This file is part of TALER
|
||||
-- Copyright (C) 2014--2022 Taler Systems SA
|
||||
-- Copyright (C) 2014--2023 Taler Systems SA
|
||||
--
|
||||
-- TALER is free software; you can redistribute it and/or modify it under the
|
||||
-- terms of the GNU General Public License as published by the Free Software
|
||||
@ -116,6 +116,22 @@ 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
|
||||
$$;
|
||||
|
||||
@ -399,29 +415,5 @@ INSERT INTO exchange_tables
|
||||
,'exchange-0002'
|
||||
,'foreign'
|
||||
,TRUE
|
||||
,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);
|
||||
,FALSE)
|
||||
;
|
||||
|
@ -25,6 +25,7 @@ CREATE TABLE partners
|
||||
,wad_fee_frac INT4 NOT NULL
|
||||
,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
|
||||
,partner_base_url TEXT NOT NULL
|
||||
,PRIMARY KEY (partner_master_pub, start_date)
|
||||
);
|
||||
COMMENT ON TABLE partners
|
||||
IS 'exchanges we do wad transfers to';
|
||||
|
@ -62,6 +62,9 @@ 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'
|
||||
|
@ -25,7 +25,7 @@ DECLARE
|
||||
BEGIN
|
||||
PERFORM create_partitioned_table(
|
||||
'CREATE TABLE %I'
|
||||
'(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
|
||||
'(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
|
||||
',reserve_pub BYTEA PRIMARY KEY'
|
||||
',wire_reference INT8 NOT NULL'
|
||||
',credit_val INT8 NOT NULL'
|
||||
|
@ -14,26 +14,25 @@
|
||||
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
--
|
||||
|
||||
CREATE FUNCTION create_table_withdraw_age_commitments(
|
||||
CREATE FUNCTION create_table_age_withdraw_commitments(
|
||||
IN partition_suffix VARCHAR DEFAULT NULL
|
||||
)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_commitments';
|
||||
BEGIN
|
||||
PERFORM create_partitioned_table(
|
||||
'CREATE TABLE %I'
|
||||
'(withdraw_age_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
|
||||
',h_commitment BYTEA PRIMARY KEY CHECK (LENGTH(h_commitment)=64)'
|
||||
'(age_withdraw_commitment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
|
||||
',h_commitment BYTEA CHECK (LENGTH(h_commitment)=64)'
|
||||
',amount_with_fee_val INT8 NOT NULL'
|
||||
',amount_with_fee_frac INT4 NOT NULL'
|
||||
',max_age_group INT2 NOT NULL'
|
||||
',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
|
||||
',reserve_sig BYTEA CHECK (LENGTH(reserve_sig)=64)'
|
||||
',max_age INT2 NOT NULL'
|
||||
',reserve_pub BYTEA CHECK (LENGTH(reserve_pub)=32)'
|
||||
',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
|
||||
',noreveal_index INT4 NOT NULL'
|
||||
',timestamp INT8 NOT NULL'
|
||||
') %s ;'
|
||||
,table_name
|
||||
,'PARTITION BY HASH (reserve_pub)'
|
||||
@ -51,8 +50,8 @@ BEGIN
|
||||
,partition_suffix
|
||||
);
|
||||
PERFORM comment_partitioned_column(
|
||||
'The maximum age group that the client commits to with this request'
|
||||
,'max_age_group'
|
||||
'The maximum age (in years) that the client commits to with this request'
|
||||
,'max_age'
|
||||
,table_name
|
||||
,partition_suffix
|
||||
);
|
||||
@ -74,76 +73,62 @@ 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_withdraw_age_commitments(
|
||||
CREATE FUNCTION constrain_table_age_withdraw_commitments(
|
||||
IN partition_suffix VARCHAR
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_commitments';
|
||||
BEGIN
|
||||
table_name = concat_ws('_', table_name, partition_suffix);
|
||||
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
' ADD PRIMARY KEY (h_commitment, reserve_pub);'
|
||||
' ADD PRIMARY KEY (h_commitment);'
|
||||
);
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
' ADD CONSTRAINT ' || table_name || '_withdraw_age_commitment_id_key'
|
||||
' UNIQUE (withdraw_age_commitment_id);'
|
||||
' 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);'
|
||||
);
|
||||
END
|
||||
$$;
|
||||
|
||||
|
||||
CREATE FUNCTION foreign_table_withdraw_age_commitments()
|
||||
CREATE FUNCTION foreign_table_age_withdraw_commitments()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_commitments';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_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
|
||||
('withdraw_age_commitments'
|
||||
,'exchange-0003'
|
||||
,'create'
|
||||
,TRUE
|
||||
,FALSE),
|
||||
('withdraw_age_commitments'
|
||||
,'exchange-0003'
|
||||
,'constrain'
|
||||
,TRUE
|
||||
,FALSE),
|
||||
('withdraw_age_commitments'
|
||||
,'exchange-0003'
|
||||
,'foreign'
|
||||
,TRUE
|
||||
,FALSE);
|
||||
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);
|
@ -14,22 +14,24 @@
|
||||
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
--
|
||||
|
||||
CREATE FUNCTION create_table_withdraw_age_reveals(
|
||||
CREATE FUNCTION create_table_age_withdraw_revealed_coins(
|
||||
IN partition_suffix VARCHAR DEFAULT NULL
|
||||
)
|
||||
RETURNS VOID
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
|
||||
BEGIN
|
||||
PERFORM create_partitioned_table(
|
||||
'CREATE TABLE %I'
|
||||
'(withdraw_age_reveals_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE
|
||||
',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=32)'
|
||||
'(age_withdraw_revealed_coins_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE
|
||||
',h_commitment BYTEA NOT NULL CHECK (LENGTH(h_commitment)=64)'
|
||||
',freshcoin_index INT4 NOT NULL'
|
||||
',denominations_serial INT8 NOT NULL'
|
||||
',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=32)'
|
||||
',coin_ev BYTEA NOT NULL'
|
||||
',h_coin_ev BYTEA CHECK (LENGTH(h_coin_ev)=64)'
|
||||
',ev_sig BYTEA NOT NULL'
|
||||
') %s ;'
|
||||
,table_name
|
||||
,'PARTITION BY HASH (h_commitment)'
|
||||
@ -59,29 +61,41 @@ BEGIN
|
||||
,partition_suffix
|
||||
);
|
||||
PERFORM comment_partitioned_column(
|
||||
'Hash of the blinded coins'
|
||||
'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)'
|
||||
,'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_withdraw_age_reveals(
|
||||
CREATE FUNCTION constrain_table_age_withdraw_revealed_coins(
|
||||
IN partition_suffix VARCHAR
|
||||
)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
|
||||
BEGIN
|
||||
table_name = concat_ws('_', table_name, partition_suffix);
|
||||
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
' ADD CONSTRAINT ' || table_name || '_withdraw_age_reveals_id_key'
|
||||
' UNIQUE (withdraw_age_reveals_id);'
|
||||
' ADD CONSTRAINT ' || table_name || '_age_withdraw_revealed_coins_id_key'
|
||||
' UNIQUE (age_withdraw_revealed_coins_id);'
|
||||
);
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
@ -91,18 +105,18 @@ BEGIN
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION foreign_table_withdraw_age_reveals()
|
||||
CREATE FUNCTION foreign_table_age_withdraw_revealed_coins()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name VARCHAR DEFAULT 'withdraw_age_reveals';
|
||||
table_name VARCHAR DEFAULT 'age_withdraw_revealed_coins';
|
||||
BEGIN
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
' ADD CONSTRAINT ' || table_name || '_foreign_h_commitment'
|
||||
' FOREIGN KEY (h_commitment)'
|
||||
' REFERENCES withdraw_age_commitments (h_commitment) ON DELETE CASCADE;'
|
||||
' REFERENCES age_withdraw_commitments (h_commitment) ON DELETE CASCADE;'
|
||||
);
|
||||
EXECUTE FORMAT (
|
||||
'ALTER TABLE ' || table_name ||
|
||||
@ -121,17 +135,17 @@ INSERT INTO exchange_tables
|
||||
,partitioned
|
||||
,by_range)
|
||||
VALUES
|
||||
('withdraw_age_reveals'
|
||||
('age_withdraw_revealed_coins'
|
||||
,'exchange-0003'
|
||||
,'create'
|
||||
,TRUE
|
||||
,FALSE),
|
||||
('withdraw_age_reveals'
|
||||
('age_withdraw_revealed_coins'
|
||||
,'exchange-0003'
|
||||
,'constrain'
|
||||
,TRUE
|
||||
,FALSE),
|
||||
('withdraw_age_reveals'
|
||||
('age_withdraw_revealed_coins'
|
||||
,'exchange-0003'
|
||||
,'foreign'
|
||||
,TRUE
|
@ -26,12 +26,14 @@ BEGIN
|
||||
PERFORM create_partitioned_table(
|
||||
'CREATE TABLE IF NOT EXISTS %I'
|
||||
'(aml_history_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
|
||||
',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
|
||||
',h_payto BYTEA 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 ;'
|
||||
@ -80,6 +82,18 @@ 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'
|
||||
@ -110,11 +124,10 @@ BEGIN
|
||||
EXECUTE FORMAT (
|
||||
'CREATE INDEX ' || table_name || '_main_index '
|
||||
'ON ' || table_name || ' '
|
||||
'(h_payto ASC, decision_time ASC);'
|
||||
'(h_payto, decision_time DESC);'
|
||||
);
|
||||
END $$;
|
||||
|
||||
-- FIXME: also have INSERT on AML decisions to update AML status!
|
||||
|
||||
INSERT INTO exchange_tables
|
||||
(name
|
||||
|
@ -30,6 +30,7 @@ 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)'
|
||||
|
@ -32,7 +32,7 @@ BEGIN
|
||||
',birthdate VARCHAR'
|
||||
',collection_time INT8 NOT NULL'
|
||||
',expiration_time INT8 NOT NULL'
|
||||
',encrypted_attributes VARCHAR NOT NULL'
|
||||
',encrypted_attributes BYTEA NOT NULL'
|
||||
') %s ;'
|
||||
,table_name
|
||||
,'PARTITION BY HASH (h_payto)'
|
||||
|
@ -96,6 +96,7 @@ 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 \
|
||||
@ -114,6 +115,8 @@ 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 \
|
||||
@ -136,7 +139,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_update_aml_officer.h pg_update_aml_officer.c \
|
||||
pg_test_aml_officer.h pg_test_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 \
|
||||
@ -255,8 +258,6 @@ 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)
|
||||
@ -291,24 +292,19 @@ libtalerexchangedb_la_LDFLAGS = \
|
||||
|
||||
|
||||
check_PROGRAMS = \
|
||||
test-exchangedb-postgres \
|
||||
test-exchangedb-postgres
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
bench-db-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
|
||||
perf_get_link_data-postgres\
|
||||
perf_select_refunds_by_coin-postgres\
|
||||
perf_reserves_in_insert-postgres \
|
||||
perf_deposits_get_ready-postgres
|
||||
|
||||
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
|
||||
TESTS = \
|
||||
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
|
||||
$(check_PROGRAMS)
|
||||
|
||||
test_exchangedb_postgres_SOURCES = \
|
||||
test_exchangedb.c
|
||||
test_exchangedb_postgres_LDADD = \
|
||||
@ -321,32 +317,6 @@ 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 = \
|
||||
@ -357,21 +327,9 @@ bench_db_postgres_LDADD = \
|
||||
-lgnunetutil \
|
||||
$(XLIB)
|
||||
|
||||
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 = \
|
||||
perf_reserves_in_insert_postgres_SOURCES = \
|
||||
perf_reserves_in_insert.c
|
||||
perf_reserves_in_insert_postgres_LDADD = \
|
||||
libtalerexchangedb.la \
|
||||
$(top_builddir)/src/json/libtalerjson.la \
|
||||
$(top_builddir)/src/util/libtalerutil.la \
|
||||
@ -382,9 +340,9 @@ test_exchangedb_populate_table_postgres_LDADD = \
|
||||
-lm \
|
||||
$(XLIB)
|
||||
|
||||
test_exchangedb_populate_link_data_postgres_SOURCES = \
|
||||
test_exchangedb_populate_link_data.c
|
||||
test_exchangedb_populate_link_data_postgres_LDADD = \
|
||||
perf_select_refunds_by_coin_postgres_SOURCES = \
|
||||
perf_select_refunds_by_coin.c
|
||||
perf_select_refunds_by_coin_postgres_LDADD = \
|
||||
libtalerexchangedb.la \
|
||||
$(top_builddir)/src/json/libtalerjson.la \
|
||||
$(top_builddir)/src/util/libtalerutil.la \
|
||||
@ -395,9 +353,9 @@ test_exchangedb_populate_link_data_postgres_LDADD = \
|
||||
-lm \
|
||||
$(XLIB)
|
||||
|
||||
test_exchangedb_populate_ready_deposit_postgres_SOURCES = \
|
||||
test_exchangedb_populate_ready_deposit.c
|
||||
test_exchangedb_populate_ready_deposit_postgres_LDADD = \
|
||||
perf_get_link_data_postgres_SOURCES = \
|
||||
perf_get_link_data.c
|
||||
perf_get_link_data_postgres_LDADD = \
|
||||
libtalerexchangedb.la \
|
||||
$(top_builddir)/src/json/libtalerjson.la \
|
||||
$(top_builddir)/src/util/libtalerutil.la \
|
||||
@ -408,5 +366,19 @@ test_exchangedb_populate_ready_deposit_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
|
||||
|
@ -25,6 +25,8 @@ 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;
|
||||
|
@ -1,184 +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_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 $$;
|
||||
|
@ -1,284 +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_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 $$;
|
@ -1,506 +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_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 $$;
|
477
src/exchangedb/exchange_do_batch_coin_known.sql
Normal file
477
src/exchangedb/exchange_do_batch_coin_known.sql
Normal file
@ -0,0 +1,477 @@
|
||||
--
|
||||
-- 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;
|
||||
$$;*/
|
@ -1,116 +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_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 $$;
|
@ -123,6 +123,7 @@ THEN
|
||||
-- Deposit exists, but with differences. Not allowed.
|
||||
out_balance_ok=FALSE;
|
||||
out_conflict=TRUE;
|
||||
out_exchange_timestamp=0;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
|
59
src/exchangedb/exchange_do_get_link_data.sql
Normal file
59
src/exchangedb/exchange_do_get_link_data.sql
Normal file
@ -0,0 +1,59 @@
|
||||
--
|
||||
-- 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 $$;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user