Compare commits

...

211 Commits

Author SHA1 Message Date
5ee567d1ba
-fix memory leak 2023-05-01 20:59:03 +02:00
84bde679a7
-typos 2023-05-01 20:19:51 +02:00
e68d9f9b75
-cleanup signatures 2023-05-01 14:22:18 +02:00
153a078ca5
-typo in comment 2023-05-01 14:12:38 +02:00
1a63275d98
WiP: age-withdraw, finished reveal-request, 10/n
- /age-withdraw/$ACH/reveal handler now fully implemented
- for consistency with api: rename of tables from withdraw_age_... to
  age_withdraw
2023-05-01 14:05:58 +02:00
Christian Grothoff
af3c92f9d5
-dox 2023-04-25 23:19:18 +02:00
Christian Grothoff
e1439e6401
-fix doxygen 2023-04-25 23:12:33 +02:00
Christian Grothoff
487f23502f
add nexus-fetch-transactions 2023-04-25 23:06:58 +02:00
Christian Grothoff
1a3dbf8c98
-fix port 2023-04-24 21:15:04 +02:00
Christian Grothoff
505170ce1f
fix filename 2023-04-24 20:37:53 +02:00
Christian Grothoff
b219366cdf
update for API change as per #6363 2023-04-23 22:57:39 +02:00
08b420dd52
-fix typos for doxygen 2023-04-23 11:55:17 +02:00
Christian Grothoff
88a69ac7f2
add missing original makefile 2023-04-22 18:33:17 +02:00
5d17c9c909
wallet expects bankAccessApiBaseUrl 2023-04-22 18:14:33 +02:00
6f492b2a97
WiP: age-withdraw, continue with finalize_age_withdraw_and_sign, 9/n
Also:

- added duplicate planchet check for age-withdraw_reveal
- added stubs for (get|insert)_age_withdraw_reveal
2023-04-22 17:18:59 +02:00
89a9224c3b
Merge branch 'master' of ssh://git.taler.net/exchange 2023-04-22 15:39:14 +02:00
12681dfa1a
WiP: age-withdraw, adjust schema and DB-handlers, cleanup FIXME's, 8/n 2023-04-22 15:39:02 +02:00
37dd5bed20
-added FIXME 2023-04-22 15:06:44 +02:00
Christian Grothoff
acbee86745
simplify batch insert: no cursor where not required, replace out_reserve_found with ruuid being allowed to remain NULL 2023-04-22 15:02:47 +02:00
Christian Grothoff
c3fc8c5e55
fix fakebank long polling 2023-04-22 14:43:26 +02:00
Christian Grothoff
76b934b219
-misc fixes 2023-04-22 02:54:55 +02:00
Christian Grothoff
be1d8afaec
-misc fixes 2023-04-22 02:33:18 +02:00
Christian Grothoff
0236caf354
-misc fixes 2023-04-22 02:30:02 +02:00
Christian Grothoff
9e61579c8b
-misc fixes 2023-04-22 02:26:44 +02:00
Christian Grothoff
89c5a3eca9
-misc bugs 2023-04-22 01:53:41 +02:00
Christian Grothoff
53157062cb
-misc bugs 2023-04-22 01:40:53 +02:00
Christian Grothoff
2dab1fac1c
misc bugfixes in reserves_in batch logic 2023-04-22 01:20:41 +02:00
Christian Grothoff
5290453e36
clean up reserve_get logic 2023-04-21 22:30:37 +02:00
Christian Grothoff
03deaeb108
-fix doxygen 2023-04-21 10:54:50 +02:00
Christian Grothoff
ee6ec1f55d
-fix bug in sync 2023-04-21 10:50:35 +02:00
Christian Grothoff
44e0e00595
fix some major bugs in pg_reserves_in_insert 2023-04-18 20:44:33 +02:00
Christian Grothoff
8952a87b85
avoid overloading of global variable 2023-04-18 20:24:10 +02:00
Christian Grothoff
8463572bea
fix SPI build 2023-04-16 22:07:36 +02:00
Christian Grothoff
ade7586c30
add missing resource 2023-04-16 21:26:01 +02:00
Christian Grothoff
10c779bbc6
add FIXME 2023-04-16 21:25:48 +02:00
Christian Grothoff
5121c6b1cf
work on lookup_records_by_table 2023-04-16 10:05:38 +02:00
Christian Grothoff
2906ded1a6
work on insert_records_by_table 2023-04-16 09:41:37 +02:00
Christian Grothoff
136d2b2e70
implement more of lookup_records_by_table 2023-04-15 23:43:20 +02:00
Christian Grothoff
376de032b5
create warnings on missing table syncs 2023-04-15 23:11:36 +02:00
Christian Grothoff
32c6999a83
update gana 2023-04-15 22:19:33 +02:00
Christian Grothoff
eec4dc80ef
always check for the entire batch being idempotent, not only when it is too late to repeat the request 2023-04-15 19:53:38 +02:00
Christian Grothoff
2c28f7ebd0
reduce max requests limit per default 2023-04-15 15:14:05 +02:00
Christian Grothoff
07a089f4f1
-fix memory leak 2023-04-15 14:38:32 +02:00
Christian Grothoff
eb2b4a131b
add logic to check signature over fees in /wire response (fixes #7802) 2023-04-13 17:30:53 +02:00
Christian Grothoff
4e9c43954e
-fix SQL query 2023-04-10 23:28:40 +02:00
Christian Grothoff
122c926493
avoid crashing, fail test instead 2023-04-10 13:51:36 +02:00
Christian Grothoff
27c9fef5ea
use LEFT JOIN as aml_status table may be empty 2023-04-10 10:52:45 +02:00
Christian Grothoff
090c532b3a
return AML status together with KYC status 2023-04-10 10:48:32 +02:00
Christian Grothoff
677ac4a5c8
return text/plain by default (fixes #7747) 2023-04-08 09:46:00 +02:00
Christian Grothoff
cbabddf013
fix #7792 2023-04-08 08:29:30 +02:00
Christian Grothoff
3137d8dc13
adding FIXME 2023-04-08 08:11:27 +02:00
Christian Grothoff
36b2cbb47e
modify logic to match
https://datatracker.ietf.org/doc/draft-nottingham-http-availability-hints/
2023-04-06 23:46:39 +02:00
Christian Grothoff
d4f9417d8c
-spelling, typos, indentation 2023-04-04 17:26:51 +02:00
Christian Grothoff
979ec38ec4
left-pad TOTP code with 0s 2023-04-02 14:12:13 +02:00
Christian Grothoff
e99450e2e2
-fix missing comments 2023-03-31 14:04:04 +02:00
Christian Grothoff
a30827fcef
-fix missing comments 2023-03-31 14:03:12 +02:00
Christian Grothoff
6eed8917c3
fix exchangedb build errors 2023-03-31 13:50:32 +02:00
Joseph
9cce35d270
New sql code for batch ensure coin known 2023-03-29 11:18:20 -04:00
Joseph
0c2d5bba55
Remove binary files 2023-03-27 10:19:44 -04:00
Joseph
6af9fd66fb
New spi files 2023-03-27 09:55:00 -04:00
Joseph
cb87b6f646
New spi files 2023-03-27 09:55:00 -04:00
Joseph
d83c2539bc
some changes for known coins 2023-03-27 09:54:59 -04:00
Joseph
fb70814d46
some changes for ensure known coin 2023-03-27 09:54:59 -04:00
Joseph
42258d5778
nothing to update 2023-03-27 09:54:59 -04:00
Joseph
39f2d441f7
Spi files 2023-03-27 09:53:51 -04:00
Joseph
5dfa56727e
New spi files 2023-03-27 09:51:09 -04:00
f87eda140c
new deb patch release 2023-03-14 12:31:07 +01:00
838f6b7f1d
-fix comments for doxygen 2023-03-13 09:17:44 +01:00
837c53552e
-typo 2023-03-13 09:10:52 +01:00
2cca5dff2a
-typos 2023-03-13 09:08:22 +01:00
6a3da22546
-typos 2023-03-13 09:01:29 +01:00
a2c70ff0c8
-typos 2023-03-13 08:53:16 +01:00
b15c8e527b
-typos 2023-03-13 08:49:38 +01:00
187ae6f8a2
WIP: age-withdraw, continue verify_commitment_and_max_age, 7/n
- coin, blinding, nonce and age restriction now derived from
  TALER_PlanchetMasterSecretP
- use max_age instead than max_age_group as argument
- Also, docs updated in other repo.
2023-03-13 00:31:49 +01:00
62da9cca27
-gana bump 2023-03-13 00:27:33 +01:00
f5080a3b91
Merge branch 'master' into age-withdraw 2023-03-12 18:50:36 +01:00
82bcd0d259
-gana bump 2023-03-12 18:50:10 +01:00
9c66f27034
WiP: age-withdraw, added TALER_age_restriction_commit_from_base, 6/n
Added TALER_age_restriction_commit_from_base in util/age_restriction.c,
to create a age commitment and proof from a coin's private key as
defined in
https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction
2023-03-12 17:21:33 +01:00
777a4c07cf
Merge branch 'master' into age-withdraw 2023-03-11 11:51:02 +01:00
e3d5672cbd
simplify hash generation of age commitment 2023-03-11 11:48:44 +01:00
257f2eb91b
WiP: age-withdraw, cut out work up to verify_commitment_and_max_age 2023-03-11 11:48:15 +01:00
ce71db2c0b
be more explicit in systemd unit file 2023-03-10 12:18:56 +01:00
4931e30948
fix typo in comment 2023-03-10 00:45:12 +01:00
Christian Grothoff
9d5549d6ba
retry on failure 2023-03-09 19:48:29 +01:00
Christian Grothoff
74facbead4
add comments 2023-03-09 19:24:04 +01:00
MS
269425672c
typo 2023-03-09 18:02:28 +01:00
Christian Grothoff
90664b555c
log URL in error message (fixes #7725) 2023-03-08 12:04:33 +01:00
Christian Grothoff
890c962817
fix #7744 2023-03-08 12:01:07 +01:00
Christian Grothoff
21c9dae382
style fix 2023-03-08 11:52:56 +01:00
5608a73c00
-simplify zero-check for age-commitment hash 2023-03-07 11:15:24 +01:00
b7e20eb71e
-minor refactoring in age_withdraw-reveal 2023-03-06 21:37:32 +01:00
7521ff1cf4
-minor refactoring in age_withdraw-reveal 2023-03-06 21:36:14 +01:00
2d1583f96b
WiP: age-withdraw implementation, part 4/n
- check dates of denominations (expiry, etc.)
- refactor denomination checks into denomination_is_valid()
2023-03-06 21:24:45 +01:00
6adc223028
WiP: age-withdraw implementation, part 4/n
- check dates of denominations (expiry, etc.)
- refactor denomination checks into denomination_is_valid()
2023-03-06 21:21:20 +01:00
20cd46f63d
Merge branch 'age-withdraw' 2023-03-06 20:51:11 +01:00
Christian Grothoff
2c78cb71e6
-fix missing /home/grothoff for taler-exchange-offline user 2023-03-06 20:50:27 +01:00
262b470878
WiP: age-withdraw implementation, part 3/n
- retrieval of previous commitment
- validity check of all denominations
- comparison of accumulated values and fees with commited value and fee.
- update gana
2023-03-06 20:42:48 +01:00
Christian Grothoff
e2deb89a3d
-fix missing /home/grothoff for taler-exchange-offline user 2023-03-06 20:10:07 +01:00
af1001bc42
-cleanup age-withdraw_reveal 2023-03-06 13:04:26 +01:00
Christian Grothoff
70645cbb1b
fix permissions, default to UNIX, improve logging 2023-03-05 01:18:08 +01:00
Christian Grothoff
e2185233f6
bump Debian version 2023-03-04 23:33:11 +01:00
Christian Grothoff
ce205f93a2
avoid interactive prompt 2023-03-04 23:18:36 +01:00
Christian Grothoff
d24423e8f6
bump Debian version 2023-03-04 23:10:38 +01:00
Christian Grothoff
442002282d
improve nginx configuration consistency 2023-03-04 23:04:27 +01:00
Christian Grothoff
b10d990afd
prepare dirs for secmod 2023-03-04 20:39:53 +01:00
Christian Grothoff
c9d0e4a473
make taler-exchange-offline a 'normal' user with shell 2023-03-04 20:33:18 +01:00
Christian Grothoff
2ad12de668
-fix FTBFS and other bugs 2023-03-04 18:19:18 +01:00
Christian Grothoff
4eb2c3e78c
debian bump 2023-03-04 18:07:22 +01:00
Christian Grothoff
dd59f3eea6
use localhost, for consistency 2023-03-04 17:43:18 +01:00
Christian Grothoff
72ad473fde
misc minor fixes 2023-03-04 17:04:12 +01:00
priscilla
263ebf00fc
totp update function 2023-03-03 05:20:40 -05:00
priscilla
b46c03b2c9
totp algorithm 2023-03-02 09:55:24 -05:00
468006c60b
WiP: age-withdraw implementation, part 2/n
Commit phase of the age-withdraw protocol implemented, according to
https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction

- added new files, forgot in previous commit
2023-03-01 11:14:30 +01:00
b4128c2c2a
WiP: age-withdraw implementation, part 1/n
Commit phase of the age-withdraw protocol implemented, according to
https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction
2023-03-01 11:11:46 +01:00
Christian Grothoff
7f518fff1a
-typo 2023-02-21 17:18:28 +01:00
Christian Grothoff
f767a9d12c
fix dist rule 2023-02-21 16:44:36 +01:00
Christian Grothoff
c6d50abecc
configure.ac version bump 2023-02-21 14:56:59 +01:00
Christian Grothoff
8f5dc40217
Debian bump 2023-02-21 14:54:51 +01:00
Christian Grothoff
4a51b9a9a1
-fix bug 2023-02-21 14:52:44 +01:00
Christian Grothoff
7da69142b4
-misc minor bugfixes 2023-02-21 14:44:31 +01:00
Christian Grothoff
95bd24916e
skeleton logic for POS confirmation 2023-02-21 12:57:33 +01:00
priscilla
b663c8a3c1
fix memory leak on pay 2023-02-20 12:17:10 -05:00
priscilla
a7f0611a88
update changes with pull 2023-02-20 12:17:10 -05:00
priscilla
3e6a6f0ee6
update 2023-02-20 12:17:09 -05:00
Christian Grothoff
b43cf6f97f
-fix comment 2023-02-20 16:59:12 +01:00
Christian Grothoff
185391f3fc
idempotency test 2023-02-19 21:51:39 +01:00
Christian Grothoff
13d90bb1a3
on idempotent deposit, just skip most of the transaction 2023-02-19 21:46:15 +01:00
Christian Grothoff
e2fe36a0be
fix typo 2023-02-19 21:05:36 +01:00
Christian Grothoff
95e3087984
expose now returned purse_expiration in exchange API 2023-02-19 18:32:38 +01:00
Christian Grothoff
9a841f6047
implement #7706 2023-02-19 18:30:04 +01:00
Christian Grothoff
d49a0536ad
-fix more memory leaks 2023-02-19 13:14:16 +01:00
Christian Grothoff
fe79f6af9c
-fix memory leak 2023-02-19 13:11:57 +01:00
Christian Grothoff
0fe0c414e2
check currency matches before proceeding 2023-02-19 12:32:41 +01:00
Christian Grothoff
b414183283
-doxygen fixes 2023-02-19 11:22:53 +01:00
Christian Grothoff
2fd87736b4
also inform about AML-triggered KYC requirement in GET deposits endpoint 2023-02-19 10:26:06 +01:00
Christian Grothoff
06e2e8022c
return KYC requirement row when AML imposed KYC requirements on withdraw; return KYC choices in new /config endpoint 2023-02-19 10:21:29 +01:00
Christian Grothoff
aa5e7d2ad5
more towards actually allowing AML decisions to trigger KYC 2023-02-17 18:24:20 +01:00
Christian Grothoff
86e0f2c70d
fix memory leak and transaction error handling related to KYC 2023-02-17 16:57:29 +01:00
Christian Grothoff
87a78c6f8c
add code to sanity-check KYC configuration and KYC decisions 2023-02-16 16:38:20 +01:00
4d2d0473c3
remove obsolete taler-crypto-worker 2023-02-15 18:32:34 +01:00
Christian Grothoff
afe3f70d33
begin API change to allow AML officers to trigger KYC process 2023-02-14 14:26:00 +01:00
Christian Grothoff
437e6ec86a
-fix test FTBFS 2023-02-14 13:01:35 +01:00
Christian Grothoff
57e2f38bd2
-fix notify_s argument passing 2023-02-13 21:12:00 +01:00
Christian Grothoff
a79e50505b
-add missing option 2023-02-13 21:05:05 +01:00
Christian Grothoff
dc40f6c679
work on AML notification logic 2023-02-13 16:00:37 +01:00
Christian Grothoff
3760d43097
tolerate NULL cleanup functions in commands in batches 2023-02-13 12:21:56 +01:00
Christian Grothoff
6db4bdbe6e
-more work on AML triggers for P2P transfers 2023-02-12 22:02:51 +01:00
174022907b
fix confusing log 2023-02-12 17:53:51 +01:00
Christian Grothoff
923ff3126e
-work on AML trigger logic 2023-02-12 14:39:54 +01:00
Christian Grothoff
19132b6716
-start on AML work (incomplete) 2023-02-09 17:54:14 +01:00
Christian Grothoff
d0b43b0e6a
fix get_link_data logic 2023-02-07 12:27:45 +01:00
Christian Grothoff
5c983bd05e
-fix negation for shutdown of PG 2023-02-06 18:37:05 +01:00
Joseph
c0e6ce7519
update with sort in c code 2023-02-06 08:58:57 -05:00
Christian Grothoff
6d3efbe900
-debugging batch reserves_in notification logic: success: pg_notify does not work, identifier vs. string 2023-02-05 21:59:41 +01:00
Christian Grothoff
f079cff4ae
-fix transaction issue in exchange test 2023-02-05 20:15:26 +01:00
Christian Grothoff
64b2bc4558
-fix FTBFS of exchange test 2023-02-05 20:13:40 +01:00
Christian Grothoff
ab03ba16e9
exchangedb: use partial index instead of materialized tables deposits_by_ready and deposits_by_matching; remove now broken code; rename benchmarks to use perf_-prefix and correspond to function they benchmark 2023-02-05 19:11:47 +01:00
Christian Grothoff
e66087987f
-add missing file 2023-02-04 21:55:56 +01:00
Christian Grothoff
5a18e955eb
-fix AML decision update logic and history fetch logic 2023-02-04 21:54:47 +01:00
Christian Grothoff
47b9ef598d
-deduplicate configs 2023-02-04 18:05:36 +01:00
Christian Grothoff
4c5394fd4d
-doxygen typos 2023-02-04 16:09:31 +01:00
Christian Grothoff
19da4bd638
add tests for new AML logic, plus related bugfixes 2023-02-04 16:01:46 +01:00
Christian Grothoff
c3243aa39f
implement testing_api_cmd_check_aml_decision 2023-02-03 14:06:17 +01:00
Christian Grothoff
f6877449eb
implement testing_api_cmd_take_aml_decision.c 2023-02-03 12:57:04 +01:00
Christian Grothoff
e0687b90f1
implement testing_api_cmd_set_officer 2023-02-03 11:29:05 +01:00
Christian Grothoff
ba3b53cd27
-refund deadline should be optional/can be 0 2023-02-02 22:21:44 +01:00
Christian Grothoff
a703171f08
add logic to parse AML decisions response 2023-02-02 17:16:13 +01:00
Christian Grothoff
f60b09f8ef
add logic to parse AML decision response 2023-02-02 17:06:47 +01:00
Christian Grothoff
3898054b10
more work on KYC/AML decision inspection 2023-02-02 14:55:56 +01:00
Christian Grothoff
eab95d0154
draft for the AML GET decision endpoint 2023-02-02 12:03:55 +01:00
Christian Grothoff
915542e69c
first draft of implementation of GET AML decisions endpoint 2023-02-02 11:40:44 +01:00
Christian Grothoff
f8ff9c996f
add 2nd AML decision lookup API (skeleton only) 2023-02-01 17:05:11 +01:00
Christian Grothoff
e469e6698e
start work on AML decision query API 2023-02-01 17:00:51 +01:00
Christian Grothoff
f0567567fe
draft new AML API 2023-02-01 16:43:02 +01:00
Christian Grothoff
d738287953
-gana bump 2023-01-31 15:33:50 +01:00
Joseph
d93006c354
update link data test 2023-01-30 09:38:16 -05:00
Joseph
09c043c177
update test link data 2023-01-30 09:12:36 -05:00
Joseph
4bb96abc97
test for link data 2023-01-30 08:45:51 -05:00
Christian Grothoff
a1c0c2fafd
add test for KYC required on reserve close 2023-01-29 21:52:07 +01:00
Christian Grothoff
e8c8aa9efe
use correct attribute name in test 2023-01-29 14:12:19 +01:00
Christian Grothoff
ff202ef296
KYC: misc. fixes to attribute attestation logic 2023-01-29 13:58:56 +01:00
Christian Grothoff
99753a5d31
KYC: fix API call and memory leak 2023-01-28 17:45:56 +01:00
Christian Grothoff
92f16aad51
-misc fixes 2023-01-28 17:08:51 +01:00
Christian Grothoff
2aff69e7ec
fix Persona attribute extraction from webhooks 2023-01-28 14:06:19 +01:00
Christian Grothoff
8c5a12302e
-debug KYC webhook logic 2023-01-28 12:20:18 +01:00
Christian Grothoff
35d50ba36a
add persona attribute conversion logic 2023-01-27 22:47:42 +01:00
Christian Grothoff
0eb6f73176
add persona attribute conversion logic 2023-01-27 22:39:16 +01:00
Christian Grothoff
85e44ceea6
work on kycaid attribute extraction 2023-01-27 17:42:56 +01:00
Christian Grothoff
59716ffdc4
add logic to store attributes 2023-01-27 16:09:25 +01:00
Christian Grothoff
d79c23aaab
add logic to store attributes 2023-01-27 15:10:25 +01:00
Christian Grothoff
6da3cbedd4
-ignore new tests 2023-01-27 14:44:32 +01:00
Christian Grothoff
32fac55f7e
update GET attest logic now that it is clear that we must store KYC attributes locally: 2023-01-27 14:43:25 +01:00
Christian Grothoff
42bd2dadcf
address DB failure error handling in KYC check 2023-01-27 14:19:14 +01:00
Christian Grothoff
c239ba6f18
address DB failure error handling in KYC check 2023-01-27 14:10:40 +01:00
Christian Grothoff
7e8e2f4317
Merge branch 'master' of git+ssh://git.taler.net/exchange 2023-01-27 10:12:08 +01:00
Christian Grothoff
f199b45e52
-fix path 2023-01-27 10:11:59 +01:00
Joseph
10cf3b3b65
fix SQL syntax 2023-01-23 09:14:25 -05:00
Joseph
54fa07f5c7
tests for refunds_by_coin and ready_deposit 2023-01-23 07:57:54 -05:00
Christian Grothoff
a273b176da
-towards storing KYC attribute data 2023-01-22 21:51:46 +01:00
Christian Grothoff
c2eee251c2
add new subcommands for AML staff management and partner exchanges 2023-01-22 19:40:47 +01:00
Christian Grothoff
d53dd753e1
-typos 2023-01-22 15:41:08 +01:00
Christian Grothoff
f221db1c03
-address FIXMEs 2023-01-22 15:24:45 +01:00
Christian Grothoff
f8bfc4dc9d
address DB FIXMEs for AML 2023-01-22 15:13:34 +01:00
Christian Grothoff
d131951fbe
add new endpoints to main dispatcher 2023-01-21 23:08:29 +01:00
Christian Grothoff
31286b66f2
-integrate new AML APIs into build 2023-01-21 21:38:50 +01:00
c1502e507b
-fix gana.git url in .gitmodules 2023-01-21 10:16:20 +01:00
Christian Grothoff
5b26bd3b83
-new testing APIs 2023-01-20 19:11:36 +01:00
Christian Grothoff
c782615262
-indent 2023-01-20 15:20:51 +01:00
Christian Grothoff
56cdb7e9e6
skeleton for adding partners 2023-01-20 15:20:19 +01:00
Christian Grothoff
ebb2601278
skeleton for AML officer update 2023-01-20 15:13:22 +01:00
Christian Grothoff
c30ee88336
skeleton for AML decision server handler 2023-01-20 14:47:02 +01:00
399 changed files with 23791 additions and 8096 deletions

2
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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.

View File

@ -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]

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -6,11 +6,11 @@
# which you can get using `taler-exchange-offline setup`.
# This is just an example, your key will be different!
# MASTER_PUBLIC_KEY = YE6Q6TR1EDB7FD0S68TGDZGF1P0GHJD2S0XVV8R2S62MYJ6HJ4ZG
MASTER_PUBLIC_KEY =
# MASTER_PUBLIC_KEY =
# Publicly visible base URL of the exchange.
# BASE_URL = https://example.com/
BASE_URL =
# BASE_URL =
# For your terms of service and privacy policy, you should specify
# an Etag that must be updated whenever there are significant
@ -20,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

View File

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

View File

@ -5,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*

View File

@ -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
;;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 - -

View File

@ -193,7 +193,7 @@ echo " DONE"
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"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,
# }' \

View File

@ -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

View File

@ -398,7 +398,7 @@ echo " DONE"
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"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,
}' \

View File

@ -400,7 +400,7 @@ echo " DONE"
# Setup merchant
echo -n "Setting up merchant"
curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "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/" \

View File

@ -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",

View File

@ -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,

View File

@ -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,

View File

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

View File

@ -248,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

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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 =

View File

@ -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 \

View File

@ -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

View File

@ -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",
&currency_round_unit)) ||
( (0 != currency_round_unit.fraction) &&
(0 != currency_round_unit.value) ) )
(TALER_amount_is_zero (&currency_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,
&currency_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",

View File

@ -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;

View File

@ -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;
};

View 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 */

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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 */

View 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 */

View 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

View 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 */

View File

@ -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);

View File

@ -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;

View 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 */

View 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

View File

@ -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));
}

View File

@ -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",

View File

@ -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

View File

@ -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,

View File

@ -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",

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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.
*

View 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 */

View 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 */

View File

@ -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! */
};
/**

View File

@ -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)),

View File

@ -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,

View File

@ -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,

View File

@ -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);
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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,

View File

@ -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 */

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -1,15 +1,15 @@
test-exchangedb-auditors
test-exchangedb-denomkeys
test-exchangedb-fees
test-exchangedb-postgres
test-exchangedb-signkeys
test-perf-taler-exchangedb
bench-db-postgres
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

View File

@ -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)
;

View File

@ -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';

View File

@ -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'

View File

@ -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'

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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)'

View File

@ -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)'

View File

@ -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

View File

@ -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;

View File

@ -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 $$;

View File

@ -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 $$;

View File

@ -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 $$;

View 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;
$$;*/

View File

@ -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 $$;

View File

@ -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;

View 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